updux/docs/tutorial.md

3.9 KiB

Tutorial

This tutorial walks you through the features of Updux using the time-honored example of the implementation of Todo list store.

We'll be using updeep to help with immutability and deep merging, but that's totally optional. If updeep is not your bag, it can easily be substitued with, say, [immer][], [lodash][], or even plain JavaScript.

Definition of the state

To begin with, let's define that has nothing but an initial state.

import { Updux } from 'updux';

const todosDux = new Updux({
    initial: {
        next_id: 1,
        todos: [],
    }
});

Congrats! You have written your first Updux object. It doesn't do a lot, but you can already create a store out of it, and its initial state will be automatically set:

const store = todosDux.createStore();

console.log(store.getState()); // prints { next_id: 1, todos: [] }

Add actions

This is all good, but a little static. Let's add actions!

const todosDux = new Updux({
    initial: {
        next_id: 1,
        todos: [],
    },
    {
        addTodo: null,
        todoDone: null,
    }
});

Accessing actions

Once an action is defined, its creator is accessible via the actions accessor.

console.log( todosDux.actions.addTodo('write tutorial') );
    // prints { type: 'addTodo', payload: 'write tutorial' }

Adding a mutation

Mutations are the reducing functions associated to actions. They are defined via the setMutation method:

```js

todosDux.setMutation( 'addTodo', description => ({next_id: id, todos}) => ({ next_id: 1 + id, todos: [...todos, { description, id, done: false }] }));

## Effects

In addition to mutations, Updux also provides action-specific middleware, here
called effects.

Effects use the usual Redux middleware signature, plus a few goodies.
The `getState` and `dispatch` functions are augmented with the dux selectors,
and actions, respectively. The selectors and actions are also available
from the api object.

```js
import u from 'updeep';
import { action, Updux } from 'updux';

// we want to decouple the increment of next_id and the creation of
// a new todo. So let's use a new version of the action 'addTodo'.

const addTodoWithId = action('addTodoWithId');
const incNextId = action('incNextId');
const addTodo = action('addTodo');

const addTodoEffect = ({ getState, dispatch }) => next => action => {
    const id = getState.nextId();

    dispatch.incNextId();

    next(action);

    dispatch.addTodoWithId({ description: action.payload, id });
}

const todosDux = new Updux({
    initial: { nextId: 1, todos: [] },
    actions: { addTodo, incNextId, addTodoWithId },
    selectors: {
        nextId: ({nextId}) => nextId,
    },
    mutations: {
        addTodoWithId: (todo) => u({ todos: (todos) => [...todos, todo] }),
        incNextId: () => u({ nextId: id => id+1 }),
    },
    effects: {
        'addTodo': addTodoEffect
    }
});

const store = todosDux.createStore();

store.dispatch.addTodo('Do the thing');

Catch-all effect

It is possible to have an effect match all actions via the special * token.

todosUpdux.addEffect('*', () => next => action => {
    console.log( 'seeing action fly by:', action );
    next(action);
});

Adding selectors

Selectors can be defined to get data derived from the state. From now you should know the drill: selectors can be defined at construction time or via setSelector.

const getTodoById = ({todos}) => targetId => todos.find(({id}) => id === targetId);

const todosUpdux = new Updux({
    selectors: {
       getTodoById
    }
})

or

todosDux.setSelector('getTodoById', getTodoById);

Accessing selectors

The getState method of a dux store is augmented with its selectors, with the first call for the state already called in for you.

const store = todosDux.createStore();

console.log(
    todosUpdux.getState.getTodoById(1)
);