Store

At its core, diox is a complete isomorphic state management system.

Concepts

diox relies on very simple concepts, but it's important to clearly understand their logic in order to use it as much efficiently as possible.

Subscriptions

A subscription is a pure function that takes an object in parameter (the state) and performs any operation with it. Subscriptions are called each time a change happens on the state.

const subscription = (newState) => {
console.log('State has changed!', newState);
};

Mutations

A mutation is also a pure function that returns a copy of an object (the state) depending on the desired type of change (mutation). In diox, mutations are in charge of performing synchronous changes on the internal state.

const MY_MUTATION = ({ state }) => {
return {
count: state.count + 1,
};
};

A good practice consists in using SNAKE_UPPERCASE syntax to name your mutations, to differentiate them from actions.

The basx library makes it easy to perform deep copies and deep merges of JavaScript objects and arrays to keep your functions pure.

Actions

An action is a function, usually asynchronous (e.g. an API call, a timer, ...), that performs one or several calls to mutations. Actions cannot update state directly as it's the mutations' job.

const myAction = ({ mutate, hash }) => {
// `hash` tells diox which mutation should be called (see Modules).
setTimeout(() => mutate(hash, 'ADD'), 500);
};

A good practice consists in using pascalCase syntax to name your actions, to differentiate them from mutations.

Modules

A module contains a part of your app's global state, managing a specific concern (e.g. list of users, list of blog articles, app status, ...). By creating several modules, and combining them, you can build complex, evolutive, infinitely scalable apps without worrying about performance. Each module is composed of a state, a set of mutations, and optionally actions.

const myModule = {
// Contains the module's initial state.
state: {
count: 0,
},
mutations: {
MY_MUTATION,
},
actions: {
myAction,
},
};

Combiners

You can mix the result of several module's changes by using a Combiner. It is a pure function that listens to changes on one or several modules, and returns a mix of their states for easier processing. For instance, imagine you have a module containing all the articles of a blog, and another one containing the list of authors. Instead of subscibing to both modules, you can create a combiner that will generate a proper structure with all info (articles + authors) so you just have to subscribe to this combiner and forget about managing several sources of data in the rest of your application.

const myCombiner = (moduleAState, moduleBState) => ({
a: moduleAState.increment,
b: moduleBState.otherKey,
});
// This will give you a "combined" state as:
// {
// a: 1,
// b: 'another value',
// }

Middlewares

Middlewares can be useful in some situations where you want to listen to any state change on all modules and trigger the same logic each time something changes. For instance, you may want to implement a "time-travel" tool, keeping a complete history of states changes over time to revert them if necessary.

const myMiddleware = (newState) => { console.log('New state!', newState); };

Store

The Store is the entity that ties everything together. Modules are registered into the Store, using a unique identifier (so-called "hash"). You can subscribe to changes on registered modules, combine modules, or apply middlewares to achieve more complex goals.

// Instanciating store...
const store = new Store();
// Adding a global middleware triggered at each state change on any module...
store.use((newState) => {
console.log('New state !', newState);
});
// Registering modules...
store.register('a', moduleA);
store.register('b', moduleB);
// Creating a combiner which mixes `a` and `b` modules...
store.combine('c', ['a', 'b'], (newAState, newBState) => ({
a: newAState.increment,
b: newBState
}));
// Subscribing to the combination of `a` and `b` modules...
store.subscribe('c', (newState) => {
console.log('New mixed state!', newState.a, newState.b);
});

Complete example

import { Store } from 'diox';
const moduleA = {
state: { increment: 0 },
mutations: {
ADD({ state }) {
return {
increment: state.increment + 1,
};
},
},
};
const moduleB = {
state: { decrement: 1000 },
mutations: {
SUB({ state }) {
return {
decrement: state.decrement - 1,
};
},
},
actions: {
asyncSub({ mutate }) {
setTimeout(() => { mutate(hash, 'SUB'); }, 1000);
},
},
};
const store : Store = new Store();
store.use((newState) => {
console.log('New state !', newState);
});
store.register('a', moduleA);
store.register('b', moduleB);
store.combine('c', ['a', 'b'], (newAState, newBState) => ({
a: newAState.increment,
b: newBState
}));
store.subscribe('a', (newState) => {
console.log('New state from a !', newState);
});
store.subscribe('c', (newState) => {
console.log('New state from c !', newState);
});
store.mutate('a', 'ADD');
store.dispatch('b', 'asyncSub');

The above example will display in console :

New state ! { increment: 0 }
New state ! { decrement: 1000 }
New state from a ! { increment: 0 }
New state from c ! { a: 0, b: { decrement: 1000 } }
New state ! { increment: 1 }
New state from a ! { increment: 1 }
New state from c ! { a: 1, b: { decrement: 1000 } }
New state ! { decrement: 999 }
New state from c ! { a: 1, b: { decrement: 999 } }