@uniono/react

React implementation of @uniono/state

Usage no npm install needed!

<script type="module">
  import unionoReact from 'https://cdn.skypack.dev/@uniono/react';
</script>

README

@uniono/react

The library is designed to work with @uniono/state in React.

useUnion

Import

import useUnion from '@uniono/react'

Description

Hook useUnion has one required argument (template: Object). Based on this template a union will be created. A similar key (for each key of the template) will be created in the union with value result of function useUnion.atom(env, template[key]).

import useUnion from '@uniono/react'

const template = {
    name: '',
    setName: ({ mutations }, newName) => mutations.name.setValue(newName)
}

// Union
// const union = {}
// union.name = new Store('')
// union.setName = new Transaction(union, ({ mutations }, event) => mutations.name.setValue(event.target.value))

const MyComponent = () => {
    const state = useUnion(template)
    const name = state.name       // Value of store union.name
    const setName = state.setName // Method execute of transaction union.setName

    return <input value={name} onChange={setName} />
}

Examples

const template = {
    description: useUnion.mutate(({ union, diMap }) => 
        `Union keys count: ${Object.keys(union)}. Global keys: ${Array.from(diMap.keys()).length}`
    ),
    getMe: useUnion.asIs(api.users.getMe),
    menu: useUnion.from({
        open: false,
        toggle: ({ value, mutations }) => mutations.open.toggle()
    }),
    store: '',                 // or useUnion.store('')
    undefinedStore: undefined, // or useUnion.store(),
    arrayStore: [],            // or useUnion.arrayStore([ ]),
    mapStore: new Map(),       // or useUnion.mapStore(new Map()),
    numberStore: 42,           // or useUnion.numberStore(42),
    booleanStore: true,        // or useUnion.booleanStore(true),
    objectStore: {},           // or useUnion.objectStore(42),
    nullStore: null,           // or useUnion.objectStore(null),
    mart: useUnion.mart(       // create mart 
        (value) => value * 2,
        ({ numberStore }) => numberStore
    ),
    transaction: ({ mutations }, value) => // create transaction
        mutations.numberStore.setValue(value),
    anotherTransaction: useUnion.transaction(
        ({ mutations }, value) => mutations.setValue(value),
        ({ numberStore }) => numberStore
    ),
    // Dependency Injection
    user: useUnion.global('user', useUnion.from({ 
        userName: '',
        setUserName: ({ mutations }, value) => mutations.userName.setValue(value)
    })),
    injection: useUnion.inject('user')
}

Template, which is used to create the next union

const union = {}
union.description = 'Union keys count: 0. Global keys: 0'
union.getMe = api.users.getMe
union.menu = {}
union.menu.open = new BooleanStore(false)
union.menu.toggle = new Transaction(union.menu, ({ mutations }) => mutations.open.toggle())
union.store = new Store('')
union.undefinedStore = new Store()
union.arrayStore = new ArrayStore([])
union.mapStore = new MapStore(new Map())
union.numberStore = new NumberStore(42)
union.booleanStore = new BooleanStore(true)
union.objectStore = new ObjectStore({})
union.nullStore = new ObjectStore(null)
union.mart = new Mart(union.numberStore, (value) => value * 2)
union.transaction = new Transaction(union, ({ mutations }, value) => mutations.numberStore.setValue(value))
union.anotherTransaction = new Transaction(union.numberStore, ({ mutations }, value) => mutations.setValue(value))
union.user = {
    userName: new Store(''),
    setUserName: new Transaction(union.user, ({ mutations }, value) => mutations.userName.setValue(value))
}
union.injection = union.user

Dependency Injection

Dependency Injection based on React Context. The application must be wrapped in DIProvider.

import { DIProvider } from '@uniono/react'
import React from 'react';
import ReactDOM from 'react-dom';

import App from './app';

ReactDOM.render(
    <React.StrictMode>
        <DIProvider>
            <App />
        </DIProvider>
    </React.StrictMode>,
    document.getElementById('root')
);

useUnion.global

Allows to set new value in DI

Argument Type Default Description
id any Injection id
atomTemplate any Injection atom template
{
    user: useUnion.global('user', useUnion.from({ 
        userName: '',
        setUserName: ({ mutations }, value) => mutations.userName.setValue(value)
    })),
    api: useUnion.global('api', useUnion.asIs(api))
}

useUnion.inject

Allow to inject atom from DI

Argument Type Default Description
id any Injection id
map function (x) => x Map injection
{
    usersApi: useUnion.inject('api', ({ users }) => users)
}

Template mutations

useUnion.mutate

useUnion.mutate designed to create an atom based on union & dependency injection state. Function fn will be called at the time of union creation with one argument { union, diMap }, where union is actual union, and diMap is Map (key -> diValue)

Argument Type Default Description
fn function Atom factory
{
    description: useUnion.mutate(({ union, diMap }) => myTemplateAtom)
}

useUnion.asIs

Allows to set value to union without any mutations

Argument Type Default Description
atom any Template atom
{
    getMe: useUnion.asIs(api.users.getMe)
}

useUnion.from

Creates new union from template

Argument Type Default Description
template object or function Union template
{
    menu: useUnion.from({
        open: false,
        toggle: ({ value, mutations }) => mutations.open.toggle()
    })
}

State fabrics

useUnion.store

Creates new Store in union

Argument Type Default Description
value any Store value
{
  str: useUnion.store(''),
  num: useUnion.store(42),
}

useUnion.mart

Creates new Mart in union

Argument Type Default Description
martFn function Function to calculate mart value
unionMap function (x) => x Function to map mart union (With this argument you can control when to recalculate mart)
{
    tab: 'all',
    tasks: [],
    visibleTasks: useUnion.mart(
        ({ tab, tasks }) => tasks
            .map((task, index) => ({ ...task, index }))
            .filter((task) => {
                switch (tab) {
                    case 'all':
                        return true

                    case 'active':
                        return task.done === false

                    case 'completed':
                        return task.done === true
                    
                    default:
                        throw new Error(`Unsupported tab ${JSON.stringify(tab)}`)
                }
        }),
        ({ tab, tasks }) => ({ tab, tasks })
    )
}

useUnion.transaction

Creates new Transaction in union

Argument Type Default Description
fn function Transaction function
unionMap function (x) => x Function to map transaction union
{
    input: '',
    tasks: [],
    addTask: useUnion.transaction(
        ({ value, mutations }) => {
            const { input } = value()
            const title = input.trim()
            if (title.length === 0) {
                return
            }
    
            mutations.input.setValue('')
            mutations.tasks.unshift({ done: false, title })
        }
    )
}

useUnion.arrayStore

Creates new ArrayStore in union

Argument Type Default Description
value Array [] Store value
{
    items: useUnion.arrayStore([ 2, 4, 8, 16 ]) 
    // or items: [ 2, 4, 8, 16 ]
}

useUnion.mapStore

Creates new MapStore in union

Argument Type Default Description
value Map new Map() Store value
{
    map: useUnion.mapStore(new Map())
    // or map: new Map()
}

useUnion.booleanStore

Creates new BooleanStore in union

Argument Type Default Description
atom Boolean false Store value
{
    visible: useUnion.booleamStore(true)
    // or visible: true
}

useUnion.numberStore

Creates new NumberStore in union

Argument Type Default Description
atom Number 0 Store value
{
    answer: useUnion.numberStore(42)
    // or answer: 42
}

useUnion.objectStore

Creates new ObjectStore in union

Argument Type Default Description
atom Object or null Store value
{
    user: useUnion.objectStore({ id: 42, login: 'vpupkin' })
    // or user: { id: 42, login: 'vpupkin' }
}