hookstores v1.10.1
⛵ hookstores
Hookstores
is an elementary Flux implementation using React
hooks and Immer
.
📦 installation
> npm i --save hookstores
Then wrap your React app with the context Provider:
import React from 'react';
import {render} from 'react-dom';
import {Hookstores} from 'hookstores';
render(
<Hookstores>
<App />
</Hookstores>,
container
);
🎨 idea
Hookstores
allows to organize your React app State in one or many stores.- Access local parts of this global state with
hooks
- You can listen to specific parts of specific stores, to allow accurate local rendering from your global app state (see the advanced section).
const ItemsContainer = props => {
const {useItemsStore} = useHookstores();
const {items} = useItemsStore();
return <ItemsComponent items={items} />;
};
🛠 setup
Here is the path to follow to setup Hookstores on your app:
- 1: write
reactions
: the way your store must react to actions. - 2: give your stores to
<Hookstores>
- 3: read stores data in your containers
- 4: dispatch actions to trigger
reactions
.
In the following, let's illustrate how to use Hookstores
with:
- a store of
Items
and its fetch function - a
container
plugged to this store, - and the
component
rendering the list of items.
Illustration will be marked with 🔍
📦 create a store
You'll have to define a store with
- an initialState,
- and a list of reactions.
const itemsStore = {
initialState: {},
reactions: []
};
export default itemsStore;
You should use one store for each feature.
(🔍 here the itemsStore
to deal with the Items
)
Hookstores
will create the actual store for each of them to:
- handle an immutable state with Immer,
- listen to actions to trigger the appropriate
reactions
, - emit updates to the containers when there are changes only.
🔆 Reactions
Here is a reaction
:
const doSomethingInThisStore = {
on: 'ACTION_TYPE',
reduce: (state, payload) => {
/*
Just update the state with your payload.
Here, `state` is the draftState used by `Immer.produce`
You store will then record your next immutable state.
*/
state.foo = 'bar';
},
perform: (parameters, getState, dispatch) => {
/*
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
*/
}
};
A
reaction
will be triggered when an action is dispatched withaction.type
===on
.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.
Here is the example for our illustrating itemsStore
/* ./features/items/store.js */
import apiCall from './fetch-items.js';
const FETCH_ITEMS = 'FETCH_ITEMS';
const initialState = {items: null};
const fetchItems = {
on: FETCH_ITEMS,
perform: async (parameters, getState) => {
// This function will be called whenever {type:FETCH_ITEMS} is dispatched.
// `getState` is provided here for convenience, to access the current store state.
const items = await apiCall(parameters);
return items;
},
reduce: (draft, payload) => {
// 'reduce' will be called after `perform` is over.
// 'perform' returns the items, so here payload === items
draft.items = payload;
}
};
const reactions = [fetchItems];
export default {initialState, reactions};
export {FETCH_ITEMS};
🏁 setup the Hookstores provider with these stores
Once all stores are ready, and pass them as stores
parameter to <Hookstores>
.
This is where you define names your stores.
Hookstores
will simply create hooks with the same names with the use
prefix.
whatheverNameStore ===> useWhatheverNameStore()
🔍 Example:
/* ./index.js */
import React from 'react';
import {render} from 'react-dom';
import {Hookstores} from 'hookstores';
import itemsStore from './features/items/store.js';
import anyOtherStore from './features/whatever/store.js';
const stores = {
itemsStore,
anyOtherStore
};
render(
<Hookstores stores={stores}>
<App />
</Hookstores>,
container
);
here Hookstores
will create those hooks:
const {useItemsStore, useAnyOtherStore} = useHookstores();
🍕 Using those stores in your containers
storeState ➡️ props
Listen to changes in a store and use in your local props by using the useXxxxStore
hook that was created for your store.
🔍 Here is the example for our illustrating itemsStore
:
/* ./features/items/container.js */
import React from 'react';
import ItemsComponent from './component';
const ItemsContainer = props => {
const {useItemsStore} = useHookstores();
const {items} = useItemsStore();
return <ItemsComponent items={items} />;
};
To listen to specific changes in a store, and update your local props only on those changes, use propsMapping
(see the advanced section).
📡 dispatching actions
Use prop drilling
from your containers to your components: pass functions dispatching the actions
import {SELECT_ITEM} from './features/items/store.js';
const ItemsContainer = props => {
const {dispatch} = useHookstores();
const selectItem = id => () => {
dispatch({
type: SELECT_ITEM,
itemId: id
});
};
return <ItemsComponent selectItem={selectItem} />;
};
🔥 advanced usage
The whole point of Hookstores
is to be able to perform extremely local rendering.
So, rather than the listening for the whole state updates, you can update rendering depending on specific updates in a store.
To do so, specify the props
mapping you want to listen for changes, telling corresponding paths in your store.
const propsMapping = {
items: 'path.to.items.within.your.store',
other: 'plop'
};
Now your props
will change only when one of these mapping is updated in the store.
const ItemsContainer = props => {
const {useItemsStore} = useHookstores();
const {items, other} = useItemsStore(propsMapping);
return <ItemsComponent items={items} other={other} />;
};
This way, on every store update, specific props will be extracted for the components, and nothing else: this will allow accurate local rendering from a global app state.
📚 motivation
read this doc
🏗️ development
local dev tips
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago