La Taverne
La Taverne
is an elementary Flux implementation to manage a global app state.
It provides an optional, yet easy integration with React using custom hooks.
🕵️ Demo
- Try live on https://taverne.uralys.com/
- Demo sources: https://github.com/uralys/taverne-website
📦 installation
> npm i --save taverne
🐿️ Instanciate your taverne with your barrels
Once your barrels are ready, you can instanciate your taverne
and dispatch
:
import createLaTaverne from 'taverne';
import books from './barrels/books';
import potions from './barrels/potions';
import handcrafts from './barrels/handcrafts';
const {dispatch, taverne} = createLaTaverne({
books,
potions,
handcrafts
});
🧬 Create a barrel
A "Barrel" is an initialState
and a list of reactions
.
const ADD_BOOK = 'ADD_BOOK';
const addBook = {
on: ADD_BOOK,
reduce: (state, payload) => {
const {book} = payload;
state.entities.push(book);
}
};
export default {
initialState: {entities: []},
reactions: [addBook]
};
export {ADD_BOOK};
🧚 Reactions
- A
reaction
will be triggered when an action is dispatched withaction.type
===on
.
const doSomethingWithThisBarrel = {
on: 'ACTION_TYPE',
reduce: (state, payload) => {
/*
Just update the state with your payload.
Here, `state` is the draftState used by `Immer.produce`
You taverne will then record your next immutable state.
*/
state.foo = 'bar';
},
perform: (parameters, dispatch, getState) => {
/*
Optional sync or async function.
It will be called before `reduce`
When it is done, reduce will receive the result in
the `payload` parameter.
You can `dispatch` next steps from here as well
*/
}
};
-
reduce
is called usingImmer
, so mutate thestate
exactly as you would with thedraftState
parameter in produce. -
If you have some business to do before reducing, for example calling an API, use the
perform
function, eithersync
orasync
.Then
reduce
will be called with the result once it's done.
🎨 React integration
La Taverne
has a context Provider <Taverne>
which provides 2 utilities:
- the
pour
hook to access your global state anywhere - the
dispatch
function
/* src/app.js */
import React from 'react';
import {render} from 'react-dom';
import {Taverne} from 'taverne/hooks';
render(
<Taverne dispatch={dispatch} taverne={taverne}>
<App id={id} />
</Taverne>,
container
);
/* src/feature/books/container.js */
import {useTaverne} from 'taverne/hooks';
const BooksContainer = props => {
const {dispatch, pour} = useTaverne();
const books = pour('books');
return <BooksComponent books={books} />;
};
See the complete React integration steps here.
You can "pour" specific parts of the "taverne", to allow accurate local rendering from your global app state.
🔆 Middlewares
You can create more generic middlewares to operate any actions.
Your middlewares must implement onDispatch: (action, dispatch, getState) => {}
const customMiddleware = taverne => {
const instance = {
onDispatch: (action, dispatch, getState) => {}
};
return instance;
};
Then instanciate La Taverne
with your list of middlewares as 2nd parameter:
const {dispatch, taverne} = createLaTaverne(barrels, [customMiddleware]);
example: plugging the redux devtools extension with this middleware
🐛 Redux devtools
Using devtools with La Taverne
provides debugging without losing performance:
- gathered debounced actions
- nested actions
- optional state filtering to improve performance
import createLaTaverne from 'taverne';
import {createDevtools} from 'taverne/middlewares';
import books from './barrels/books';
const devtools = createDevtools();
const {dispatch, taverne} = createLaTaverne({books}, [devtools]);
When your app state is too big, you'll hit performance issues with Redux dev tools.
In this case you may need to skip part of state from tracking;
const devtools = createDevtools({
applyStateFiltering? : state => ({
...state,
hugeObject: '<skipped>'
})
});
🏗️ development
- 📓 Few local dev notes for the curious ones.
- ✅ Issues and PR Welcomed!