updux/README.md

227 lines
5.5 KiB
Markdown
Raw Permalink Normal View History

2019-10-17 15:15:10 +00:00
# What's Updux?
2019-10-24 15:37:27 +00:00
So, I'm a fan of [Redux](https://redux.js.org). Two days ago I discovered
2020-06-19 23:29:12 +00:00
[rematch](https://rematch.github.io/rematch) alonside a few other frameworks built atop Redux.
2019-10-17 15:15:10 +00:00
2020-06-19 23:29:12 +00:00
It has a couple of pretty good ideas that removes some of the
boilerplate. Keeping mutations and asynchronous effects close to the
reducer definition? Nice. Automatically infering the
2019-10-17 15:15:10 +00:00
actions from the said mutations and effects? Genius!
But it also enforces a flat hierarchy of reducers -- where
is the fun in that? And I'm also having a strong love for
2019-10-24 15:37:27 +00:00
[Updeep](https://github.com/substantial/updeep), so I want reducer state updates to leverage the heck out of it.
2019-10-17 15:15:10 +00:00
2019-11-05 01:34:14 +00:00
All that to say, say hello to `Updux`. Heavily inspired by `rematch`, but twisted
to work with `updeep` and to fit my peculiar needs. It offers features such as
2020-06-19 23:29:12 +00:00
- Mimic the way VueX has mutations (reducer reactions to specific actions) and
2019-11-05 01:34:14 +00:00
effects (middleware reacting to actions that can be asynchronous and/or
have side-effects), so everything pertaining to a store are all defined
in the space place.
2020-06-19 23:29:12 +00:00
- Automatically gather all actions used by the updux's effects and mutations,
2019-11-05 01:34:14 +00:00
and makes then accessible as attributes to the `dispatch` object of the
store.
2020-06-19 23:29:12 +00:00
- Mutations have a signature that is friendly to Updux and Immer.
- Also, the mutation signature auto-unwrap the payload of the actions for you.
- TypeScript types.
2019-11-05 01:34:14 +00:00
Fair warning: this package is still very new, probably very buggy,
definitively very badly documented, and very subject to changes. Caveat
Maxima Emptor.
2019-10-17 15:15:10 +00:00
2019-10-19 17:11:30 +00:00
# Synopsis
2019-11-05 01:34:14 +00:00
2019-10-19 17:11:30 +00:00
```
import updux from 'updux';
import otherUpdux from './otherUpdux';
const {
initial,
reducer,
actions,
middleware,
createStore,
2020-06-19 23:29:12 +00:00
} = new Updux({
2019-10-20 13:52:39 +00:00
initial: {
counter: 0,
},
2019-10-19 17:11:30 +00:00
subduxes: {
otherUpdux,
},
mutations: {
2019-10-20 13:52:39 +00:00
inc: ( increment = 1 ) => u({counter: s => s + increment })
2019-10-19 17:11:30 +00:00
},
effects: {
2019-10-20 13:52:39 +00:00
'*' => api => next => action => {
console.log( "hey, look, an action zoomed by!", action );
next(action);
};
2019-10-19 17:11:30 +00:00
},
2019-10-22 22:10:45 +00:00
actions: {
2020-06-19 23:29:12 +00:00
customAction: ( someArg ) => ({
type: "custom",
payload: { someProp: someArg }
2019-11-05 01:34:14 +00:00
}),
2019-10-22 22:10:45 +00:00
},
2019-10-19 17:11:30 +00:00
2019-10-20 13:52:39 +00:00
});
const store = createStore();
store.dispatch.inc(3);
2019-10-19 17:11:30 +00:00
```
# Description
Full documentation can be [found here](https://yanick.github.io/updux/).
2020-06-19 23:29:12 +00:00
Right now the best way to understand the whole thing is to go
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
## Exporting upduxes
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
If you are creating upduxes that will be used as subduxes
by other upduxes, or as
[ducks](https://github.com/erikras/ducks-modular-redux)-like containers, I
recommend that you export the Updux instance as the default export:
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
```
import Updux from 'updux';
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
const updux = new Updux({ ... });
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
export default updux;
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
Then you can use them as subduxes like this:
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
import Updux from 'updux';
import foo from './foo'; // foo is an Updux
import bar from './bar'; // bar is an Updux as well
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
const updux = new Updux({
2019-10-19 17:11:30 +00:00
subduxes: {
2019-11-05 01:34:14 +00:00
foo, bar
2019-10-19 17:11:30 +00:00
}
});
2019-10-24 15:37:27 +00:00
```
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
Or if you want to use it:
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
import updux from './myUpdux';
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
const {
reducer,
actions: { doTheThing },
createStore,
middleware,
} = updux;
2019-10-19 17:11:30 +00:00
```
## Mapping a mutation to all values of a state
Say you have a `todos` state that is an array of `todo` sub-states. It's easy
enough to have the main reducer maps away all items to the sub-reducer:
```
const todo = new Updux({
mutations: {
review: () => u({ reviewed: true}),
done: () => u({done: true}),
},
});
const todos = new Updux({ initial: [] });
2020-06-19 23:29:12 +00:00
todos.addMutation(
todo.actions.review,
(_,action) => state => state.map( todo.upreducer(action) )
);
todos.addMutation(
2020-06-19 23:29:12 +00:00
todo.actions.done,
(id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))),
);
```
But `updeep` can iterate through all the items of an array (or the values of
an object) via the special key `*`. So the todos updux above could also be
written:
```
const todo = new Updux({
mutations: {
review: () => u({ reviewed: true}),
done: () => u({done: true}),
},
});
const todos = new Updux({
subduxes: { '*': todo },
});
todos.addMutation(
2020-06-19 23:29:12 +00:00
todo.actions.done,
(id,action) => u.map(u.if(u.is('id',id), todo.upreducer(action))),
true
);
```
The advantages being that the actions/mutations/effects of the subdux will be
2020-06-19 23:29:12 +00:00
imported by the root updux as usual, and all actions that aren't being
overridden by a sink mutation will trickle down automatically.
2019-11-05 01:34:14 +00:00
## Usage with Immer
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
While Updux was created with Updeep in mind, it also plays very
well with [Immer](https://immerjs.github.io/immer/docs/introduction).
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
For example, taking this basic updux:
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
import Updux from 'updux';
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
const updux = new Updux({
initial: { counter: 0 },
2019-10-19 17:11:30 +00:00
mutations: {
2020-06-19 23:29:12 +00:00
add: (inc=1) => state => { counter: counter + inc }
2019-10-19 17:11:30 +00:00
}
});
2020-06-19 23:29:12 +00:00
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
Converting it to Immer would look like:
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
import Updux from 'updux';
import { produce } from 'Immer';
2019-10-19 17:11:30 +00:00
2019-11-05 01:34:14 +00:00
const updux = new Updux({
initial: { counter: 0 },
2019-10-22 22:10:45 +00:00
mutations: {
2020-06-19 23:29:12 +00:00
add: (inc=1) => produce( draft => draft.counter += inc ) }
2019-10-22 22:10:45 +00:00
}
});
2020-06-19 23:29:12 +00:00
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
But since typing `produce` over and over is no fun, `groomMutations`
can be used to wrap all mutations with it:
2019-10-19 17:11:30 +00:00
```
2019-11-05 01:34:14 +00:00
import Updux from 'updux';
import { produce } from 'Immer';
2019-10-17 15:15:10 +00:00
2019-11-05 01:34:14 +00:00
const updux = new Updux({
initial: { counter: 0 },
groomMutations: mutation => (...args) => produce( mutation(...args) ),
2019-10-17 15:15:10 +00:00
mutations: {
2020-06-19 23:29:12 +00:00
add: (inc=1) => draft => draft.counter += inc
2019-10-17 15:15:10 +00:00
}
});
2020-06-19 23:29:12 +00:00
```