@uralys/hookstores v1.1.0
āµ hookstores
Hookstores
is an elementary Flux implementation using React hooks.
š motivation
React is no more a
View
lib, it's now (v17) a complete framework: so either we pick a lighter lib for theView
, or choosing React ā we shouldn't need to use an additional external framework such as Redux, MobX, RxJs, Recoil, Jotail...A first approach could be to use local states and sporadic use of React context, like explained here by Kent C. Dodds, but ā it's not a proper Flux implementation, I'd rather have my entire app state fully separated from the
View
, and "connect" containers, mapping sub-states to the views, the way Redux allows to.Using React context for a global app state, like suggested here by Rico Sta. Cruz, or here by Ebenezer Don, would be ok for a small application, but would quickly lead to ā tons of useless re-renderings. That would eventually lead to lots of specific
useMemo
on every component requiring performance optimisation. So rather than to put the effort on developping on a proper state/component architecture, your effort will be spent on ā writing thoseuseMemo
everywhere.
š§ experimentation
The idea with hookstores
is to implement a simple Flux architecture
- ā splitting the the global state into stores,
- ā
applying local rendering, by mapping these stores to containers, using React hooks
useState
anduseEffect
. - ā
using React context only to provide the
Dispatcher
everywhere, andStoresProvider
who is emitting events to listeners of specific stores only.
ā¢ļø disclaimer
So yes, somehow it ends up as another lib to manage your React state š.
But since it's only few files you should understand what's behind the hood, then use and tweak them to your convenience within your own React app rather than use it out of the box.
Furthermore,
- ā ļø it's not written in typescript š
- ā ļø there are no tests š„
That being said,
- ā I'm confidently using this implementation between many apps,
- ā so I prefer to have this package,
- ā so why not sharing this experiment.
š¦ installation
> npm i --save @uralys/hookstores
setup
use Dispatcher
and StoresProvider
context providers to compose your root <App/>
import {Dispatcher, StoresProvider} from '@uralys/hookstores';
<Dispatcher>
<StoresProvider>
<App />
</StoresProvider>
</Dispatcher>
usage
In the following, let's illustrate how to use hookstores with a store of Items
, with a fetch function, a container
plugged to this store, and the component
rendering the list of items.
prepare descriptions
Describe all your stores:
- you should use one store for one feature (here the
items
) - define within
computeAction
how a store must update its state for everyhandledAction
:
/* ./features/items/store-description.js */
const FETCH_ITEMS = 'FETCH_ITEMS';
const computeAction = async (currentState, action) => {
let newState;
switch (action.type) {
case FETCH_ITEMS: {
const items = await fetchItems();
newState = {...currentState, items};
break;
}
default:
newState = {...currentState};
}
return newState;
};
const itemsStoreDescription = {
name: 'itemsStore',
initialState: {items: null},
handledActions: [FETCH_ITEMS],
computeAction
};
export default itemsStoreDescription;
export {FETCH_ITEMS};
create stores
1 - First thing the <App>
has to do is to instanciate all stores.
They will be registered and will listen to all dispatched
actions through the Dispatcher
.
2 - Compose your containers with every store you need
Then, everytime they compute an action and update their state, they notify all connected containers.
import {useStores, withStore} from '@uralys/hookstores';
import itemsStoreDescription from './features/items/store-description';
const storesDescriptions = [itemsStoreDescription];
const {createStores} = useStores();
const App = () => {
const {createStores} = useStores();
createStores(storesDescriptions);
const ItemsWithStores = withStore(itemsStoreDescription)(ItemsContainer);
return <ItemsWithStores />;
};
apply state changes to components props
use connectStore
to register to store changes on component mounting.
import React, {useLayoutEffect, useState} from 'react';
import ItemsComponent from './component';
const ItemsContainer = props => {
const [items, setItems] = useState();
const {itemsStore} = props;
useLayoutEffect(() => {
const onStoreUpdate = storeState => {
setItems(storeState.items);
};
const disconnect = connectStore(itemsStore, onStoreUpdate);
return disconnect;
}, []);
return <ItemsComponent items={items} />;
};
š don't forget to return the disconnect
function at the end of your hook, unless you may have stores updates triggering unmounted containers updates.
dispatch actions from containers
use prop drilling
from your containers to your components: pass functions dispatching the actions
import {SELECT_ITEM} from 'path/to/actions';
const ItemsContainer = props => {
const {dispatch} = useDispatcher();
const selectItem = id => () => {
dispatch({
type: SELECT_ITEM,
itemId: id
});
};
return <ItemsComponent selectItem={selectItem} {...componentProps} />;
};
šļø development
local dev tips