terminal mutations
This commit is contained in:
parent
82e8ba7385
commit
13c9603251
@ -1,2 +1,3 @@
|
||||
* [Home](/)
|
||||
* [ Tutorial ](tutorial.md)
|
||||
* [ Recipes ](recipes.md)
|
||||
|
96
docs/recipes.md
Normal file
96
docs/recipes.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Recipes
|
||||
|
||||
## Mapping a mutation to all values of a state
|
||||
|
||||
Say you have a `todos` state that is an array of `todo` sub-states, with some
|
||||
actions that should percolate to all todos, and some that should only
|
||||
percolate to one. One way to model this is via updux's splat subduxes
|
||||
(backed by `updeep`'s own '*'-key behavior).
|
||||
|
||||
```
|
||||
const done = () => (state) => ({...state, done: true});
|
||||
|
||||
const todo = new Updux({
|
||||
initial: { id: 0, done: false },
|
||||
actions: {
|
||||
done: null,
|
||||
doneAll: null,
|
||||
},
|
||||
mutations: {
|
||||
done,
|
||||
doneAll: done,
|
||||
},
|
||||
});
|
||||
|
||||
const todos = new Updux({
|
||||
initial: [],
|
||||
subduxes: { '*': todo },
|
||||
actions: { addTodo: null },
|
||||
mutations: {
|
||||
addTodo: text => state => [ ...state, { text } ]
|
||||
}
|
||||
});
|
||||
|
||||
todos.setMutation(
|
||||
todo.actions.done,
|
||||
(text,action) => u.map(u.if(u.is('text',text), todo.upreducer(action))),
|
||||
true // prevents the subduxes mutations to run automatically
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
## Usage with Immer
|
||||
|
||||
While Updux was created with Updeep in mind, it also plays very
|
||||
well with [Immer](https://immerjs.github.io/immer/docs/introduction).
|
||||
|
||||
For example, taking this basic updux:
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
|
||||
const updux = new Updux({
|
||||
initial: { counter: 0 },
|
||||
mutations: {
|
||||
add: (inc=1) => state => ({ counter: state.counter + inc })
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Converting it to Immer would look like:
|
||||
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
import { produce } from 'immer';
|
||||
|
||||
const updux = new Updux({
|
||||
initial: { counter: 0 },
|
||||
mutations: {
|
||||
add: (inc=1) => produce( draft => draft.counter += inc ) }
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
But since typing `produce` over and over is no fun, `groomMutations`
|
||||
can be used to wrap all mutations with it:
|
||||
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
import { produce } from 'immer';
|
||||
|
||||
const updux = new Updux({
|
||||
initial: { counter: 0 },
|
||||
groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||
mutations: {
|
||||
add: (inc=1) => draft => draft.counter += inc
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
54
docs/recipes.test.js
Normal file
54
docs/recipes.test.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import u from 'updeep';
|
||||
import { Updux } from '../src/index.js';
|
||||
|
||||
const done = () => (state) => ({...state, done: true});
|
||||
|
||||
const todo = new Updux({
|
||||
initial: { id: 0, done: false },
|
||||
actions: {
|
||||
done: null,
|
||||
doneAll: null,
|
||||
},
|
||||
mutations: {
|
||||
done,
|
||||
doneAll: done,
|
||||
},
|
||||
});
|
||||
|
||||
const todos = new Updux({
|
||||
initial: [],
|
||||
subduxes: { '*': todo },
|
||||
actions: { addTodo: null },
|
||||
mutations: {
|
||||
addTodo: text => state => [ ...state, { text } ]
|
||||
}
|
||||
});
|
||||
|
||||
todos.setMutation(
|
||||
todo.actions.done,
|
||||
(text,action) => u.map(u.if(u.is('text',text), todo.upreducer(action))),
|
||||
true // prevents the subduxes mutations to run automatically
|
||||
);
|
||||
|
||||
test( "tutorial", async () => {
|
||||
const store = todos.createStore();
|
||||
|
||||
store.dispatch.addTodo('one');
|
||||
store.dispatch.addTodo('two');
|
||||
store.dispatch.addTodo('three');
|
||||
|
||||
store.dispatch.done( 'two' );
|
||||
|
||||
expect( store.getState()[1].done ).toBeTruthy();
|
||||
expect( store.getState()[2].done ).toBeFalsy();
|
||||
|
||||
store.dispatch.doneAll();
|
||||
|
||||
expect( store.getState().map( ({done}) => done ) ).toEqual([
|
||||
true, true, true
|
||||
]);
|
||||
|
||||
});
|
||||
|
10
src/Updux.js
10
src/Updux.js
@ -107,9 +107,11 @@ export class Updux {
|
||||
* updux if not already present (the idea being that making a typo on a string
|
||||
* is easy, but passing a wrong function very more unlikely).
|
||||
* @param {Function} mutation - Mutating function.
|
||||
* @param {bool} terminal - If true, subduxes' mutations won't be invoked on
|
||||
* the action.
|
||||
* @return {void}
|
||||
*/
|
||||
setMutation(action, mutation) {
|
||||
setMutation(action, mutation, terminal = false) {
|
||||
// TODO option strict: false to make it okay to auto-create
|
||||
// the actions as strings?
|
||||
if (action.type) {
|
||||
@ -128,6 +130,12 @@ export class Updux {
|
||||
throw new Error(`action '${action}' is not defined`);
|
||||
}
|
||||
|
||||
if( terminal ) {
|
||||
const originalMutation = mutation;
|
||||
mutation = (...args) => originalMutation(...args);
|
||||
mutation.terminal = true;
|
||||
}
|
||||
|
||||
this.#mutations[action] = mutation;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,8 @@ const subMutations = (subduxes) => (action) => (state) => {
|
||||
|
||||
export function buildUpreducer(mutations, subduxes) {
|
||||
return (action) => (state) => {
|
||||
state = subMutations(subduxes)(action)(state);
|
||||
if( ! mutations[action.type]?.terminal )
|
||||
state = subMutations(subduxes)(action)(state);
|
||||
|
||||
return localMutation(mutations)(action)(state);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user