terminal mutations
This commit is contained in:
parent
82e8ba7385
commit
13c9603251
@ -1,2 +1,3 @@
|
|||||||
* [Home](/)
|
* [Home](/)
|
||||||
* [ Tutorial ](tutorial.md)
|
* [ 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
|
* 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).
|
* is easy, but passing a wrong function very more unlikely).
|
||||||
* @param {Function} mutation - Mutating function.
|
* @param {Function} mutation - Mutating function.
|
||||||
|
* @param {bool} terminal - If true, subduxes' mutations won't be invoked on
|
||||||
|
* the action.
|
||||||
* @return {void}
|
* @return {void}
|
||||||
*/
|
*/
|
||||||
setMutation(action, mutation) {
|
setMutation(action, mutation, terminal = false) {
|
||||||
// TODO option strict: false to make it okay to auto-create
|
// TODO option strict: false to make it okay to auto-create
|
||||||
// the actions as strings?
|
// the actions as strings?
|
||||||
if (action.type) {
|
if (action.type) {
|
||||||
@ -128,6 +130,12 @@ export class Updux {
|
|||||||
throw new Error(`action '${action}' is not defined`);
|
throw new Error(`action '${action}' is not defined`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( terminal ) {
|
||||||
|
const originalMutation = mutation;
|
||||||
|
mutation = (...args) => originalMutation(...args);
|
||||||
|
mutation.terminal = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.#mutations[action] = mutation;
|
this.#mutations[action] = mutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ const subMutations = (subduxes) => (action) => (state) => {
|
|||||||
|
|
||||||
export function buildUpreducer(mutations, subduxes) {
|
export function buildUpreducer(mutations, subduxes) {
|
||||||
return (action) => (state) => {
|
return (action) => (state) => {
|
||||||
|
if( ! mutations[action.type]?.terminal )
|
||||||
state = subMutations(subduxes)(action)(state);
|
state = subMutations(subduxes)(action)(state);
|
||||||
|
|
||||||
return localMutation(mutations)(action)(state);
|
return localMutation(mutations)(action)(state);
|
||||||
|
Loading…
Reference in New Issue
Block a user