README
smx
Create todo app
Create states
// app/states.js
import {state} from 'smx';
// create todoListState with empty array as default value
export const todoListState = state([]);
Define effects to mutate todoListState
// app/effects.js
import {effect} from 'smx';
import {todoListState} from './states';
export const addTodo = effect(
// effect body is just tuple [state, reducer]
[
// specific state need to be mutated
todoListState,
// state reducer
(
// current state
state,
// effect payload
{text},
) =>
// return next state
// keep state is immutable
state.concat({
id: Math.random(),
text,
completed: false,
}),
],
);
export const removeTodo = effect([
todoListState,
(state, {id}) => state.filter((x) => x.id !== id),
]);
export const toggleTodo = effect([
todoListState,
(state, {id}) =>
state.map((x) => (x.id === id ? {...x, completed: !x.completed} : x)),
]);
Define filtered todo state
import {state} from 'smx';
export const filteredTodoListState = state((filter) => {
// access todos from todoListState
// that means when todoListState changed, filteredTodoListState will change as well
const todos = todoListState.value();
switch (filter) {
case 'active':
return todos.filter((x) => !x.completed);
case 'completed':
return todos.filter((x) => x.completed);
default:
return todos;
}
});
// using state(arg1, arg2, ...argN) to get state family
// active todos
console.log(filteredTodoListState('active').value());
// completed todos
console.log(filteredTodoListState('completed').value());
Loading todo list from server
// convert todoListState to async state
import {state} from 'smx';
// create todoListState with empty array as default value
export const todoListState = state(
// if we poss async function to state factory, it will create async state
async () => {
const data = fetch('api_url').then((res) => res.json());
return data;
},
);
console.log(todoListState.value()); // Promise of todo array
// we should update filteredTodoListState
export const filteredTodoListState = state(async (filter) => {
const todos = await todoListState.value();
switch (filter) {
case 'active':
return todos.filter((x) => !x.completed);
case 'completed':
return todos.filter((x) => x.completed);
default:
return todos;
}
});
Handling todoListState changing
import {todoListState} from './states';
todoListState.on(({value}) => console.log('todo-list changed', value));
Handling effects triggering
import {addTodo} from './effects';
addTodo.on(() => console.log('add-todo triggered'));
Display useful info
const countMapper = (todos) => todos.length;
// using map(mapper) to create new state that map current state value to new value.
// When original state changed, mapped state value changed as well
const todoCountState = todoListState.map(countMapper);
// using mapAll(mapper) to apply mapper to all state family
const filteredTodoCountState = filteredTodoListState.mapAll(countMapper);
console.log(await countMapper.value()); // display number of all todos
console.log(await filteredTodoCountState('active').value()); // display number active todos
console.log(await filteredTodoCountState('completed').value()); // display number completed todos
Using generator in effect
We can use effect generator to listen effect triggering or state changing
yield [[ effectOrState1, effectOrState2, ... ]];
Listen multiple single state changing / effect triggering, next statement will run when all effects triggered with specified order
import {effect} from 'smx';
const up = effect(),
down = effect(),
left = effect(),
right = effect(),
pressA = effect(),
pressB = effect();
const konamiCodeEpic = effect(function* () {
// wait until below effects triggered
yield [[up, up, down, down, left, right, left, right, pressB, pressA]];
console.log('You have got 30 lives');
});
konamiCodeEpic();
yield { effectOrState1, effectOrState2, ... };
Listen multiple single state changing / effect triggering, next statement will run when one of those effects triggered
const effect1 = effect(),
effect2 = effect();
const logEpic = effect(function* () {
const triggeredEffect = yield {effect1, effect2};
if (triggeredEffect === effect1) {
console.log('effect1 triggered');
} else if (triggeredEffect === effect2) {
console.log('effect2 triggered');
}
});
yield [{ effectOrState1, effectOrState2, ... }];
Listen multiple single state changing / effect triggering, next statement will run when all effects triggered
const effect1 = effect(),
effect2 = effect();
const logEpic = effect(function* () {
yield [{effect1, effect2}];
console.log('effect1 and effect2 triggered');
});
yield effectOrState;
Listen single state changing / effect triggering
const addTodo = effect();
const logEpic = effect(function* () {
yield addTodo;
console.log('new todo added successfully');
});
yield [funcOrEffect, ...args];
Invoke function or effect with specified args
function navigateTo(route) {}
const addTodo = effect();
effect(function* () {
yield [addTodo, 'first item'];
yield [navigateTo, '/todo-list'];
});
yield [state, valueOrReducer];
Mutate state
const count = state(0);
effect(function* () {
yield [count, 1];
yield [count, (value) => value + 1];
});
yield [ [state1, valueOrReducer], [state2, valueOrReducer], [effect, ...args] ];
Perform multiple actions (state mutation, effect triggering)
const count = state(0);
effect(function* () {
yield [
[count, 1],
[count, (value) => value + 1],
];
});