BestPrice state management library
Getting started
Install
yarn add @thebestco/store-manager
Testing
yarn test
Documentation
makeResourceSubStore
Is going to be used for sub-stores like authentication, user
usage
All the parameters except the subStoreName are optional.
const userSubStore = makeResourceSubStore('user', {
getInitialState,
reducer,
actions,
sagas,
actionTypes,
});
default initial state
{
loading: true,
loaded: false,
processing: false,
pristine: {},
errors: {}
}
default actions
export const initialize = (values, { merge } = {}) => {};
export const update = (attr, value) => {};
export const updateBatch = values => {};
export const updateOnChange = ({ target }) => {};
export const reset = () => {};
export const startFetch = () => {};
export const doneFetch = payload => {};
export const failFetch = () => {};
export const startProcess = () => {};
export const doneProcess = () => {};
export const resetPristine = () => {};
export const resetPristineKey = attr => {};
makeCollectionSubStore
Should be used for sub-stores like products, images, etc. The sub store should contain pre-defined actions for both collection and collection resource handling.
usage
All the parameters except the subStoreName are optional.
const productsSubStore = makeCollectionSubStore('products', {
getInitialState,
reducer,
actions,
sagas,
actionTypes,
resourceOptions: {
getInitialState,
},
});
default initial state
{
loading: true,
loaded: false,
processing: false,
byId: {},
ids: []
}
default resource initial state
{
loading: true,
loaded: false,
processing: false,
pristine: {},
errors: {}
}
default actions
export const initialize = (values, { merge } = {}) => {};
export const update = (attr, value) => {};
export const updateBatch = values => {};
export const updateOnChange = ({ target }) => {};
export const reset = () => {};
export const startFetch = () => {};
export const doneFetch = payload => {};
export const failFetch = () => {};
export const startProcess = () => {};
export const doneProcess = () => {};
export const resetPristine = () => {};
export const resetPristineKey = attr => {};
// Resource
export const initializeResource = (id, values, { merge } = {}) => {};
export const updateResource = (id, attr, value) => {};
export const updateBatchResource = (id, values) => {};
export const updateOnChangeResource = (id, { target }) => {};
export const removeResource = id => {};
export const resetResource = id => {};
export const startFetchResource = id => {};
export const doneFetchResource = (id, payload) => {};
export const failFetchResource = id => {};
export const startProcessResource = id => {};
export const doneProcessResource = id => {};
export const resetPristineResource = id => {};
export const resetPristineKeyResource = (id, attr) => {};
makeStoreManager
const store = makeStoreManager([userSubStore, productSubStore]);
const {
actions,
actionTypes,
sagas,
subStoreNames,
reducer,
context,
useDispatch,
useSelector,
useSubStoreSelector,
useResourceSelector,
useStore,
addSubStores,
removeSubStores,
updateSagas,
reset,
} = store;
actions.user.startProcess(); // {type: 'USER_START_PROCESS'}
actionTypes.user.startProcess; // 'USER_START_PROCESS'
const Foo = () => {
const name = useSubStoreSelector('user', state => state.name);
const dispatch = useDispatch();
dispatch.user.stopProcess();
};
Code samples
Extending the sub stores
// products/provider/actionTypes.js
export default LAST_SEEN = 'PRODUCTS_LAST_SEEN';
// products/provider/actions.js
export const setLastSeen = id => ({
type: actions.LAST_SEEN,
id,
});
// products/provider/reducer.js
export const reducer = {
[actions.LAST_SEEN]: (state, { id }) => ({ ...state, lastSeen: id }),
};
// products/provider/getInitialState.js
export default () => ({ lastSeen: null });
const subStore = makeCollectionSubStore('products', {
getInitialState, // collection's initial state
reducer,
actions,
resourceOptions: {
getInitialState, // resource's initial state
reducer, // resource's reducer
actionTypes, // resource's action types
},
});
Pristine state
The store tracks the fields that have changed since the last doneFetch/doneFetchResource. If there is a change a field, we should keep the pristine value in a pristine object.
- The resetResource/reset actions should set the pristine values back to the state, and clear the pristine state
- The initializeResource/initialize/doneFetchResource/doneFetch should reset the pristine state
- The pristine updates should apply on the update/batchUpdate/updateResource/batchUpdateResource actions
Example
[productID]: {loaded: true, loading: false, errors: {}, pristine: {}, name: 'Tyler', lastname: 'Durden}
- updateResource(id, 'name', 'Norton')
[productID]: {loaded: true, loading: false, errors: {}, pristine: {name: 'Tyler'}, name: 'Norton', lastname: 'Durden}
- updateResource(id, 'name', 'Edward')
[productID]: {loaded: true, loading: false, errors: {}, pristine: {name: 'Tyler'}, name: 'Edward', lastname: 'Durden}
- updateResource(id, 'name', 'Tyler')
[productID]: {loaded: true, loading: false, errors: {}, pristine: {}, name: 'Tyler', lastname: 'Durden}
Create the root provider
const {
actions,
actionTypes,
subStoreNames,
sagas,
reducer,
context,
useDispatch,
useSelector,
useSubStoreSelector,
useResourceSelector,
useStore,
addSubStores,
removeSubStores,
updateSagas,
reset,
} = makeStore([subStore]);
// re-export useDispatch, useSelector, useResourceSelector, context...
const makeReduxStore = () => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
{},
composeWithDevTools({ name: "AppName" })(applyMiddleware(sagaMiddleware))
);
store.runSaga = sagaMiddleware.run;
updateSagas(reduxStore.runSaga);
return store;
};
const AppName = () => {
const store = useMemo(() => makeReduxStore(), []);
return (
<Provider store={store} context={context}>
<App {...props} />
</Provider>
);
};
Using the providers
import { useSelector, useDispatch } from '......provider';
const LastSeen = ({ id }) => {
const loading = useSelector(state => state.products.loading);
const dispatch = useDispatch();
if (loading) {
return <Loading />;
}
return <Button onClick={() => dispatch.products.setLastSeen(id)} />;
};
Add sub-stores dynamically
Add sub store and execute the sagas.
const subStores = [subStore1, subStore2, subStore3];
export default () => {
const store = getStore();
storeManager.addSubStores(store, store.runSaga, subStores);
};
Remove sub-stores dynamically
Remove given sub-stores.
// Substores is an array of instances.
const subStores = [subStore1, subStore2, subStore3];
// But can be also an array of strings of the substore names.
const subStoreNames = ['subStore1', 'subStore2', 'subStore3'];
export default () => {
const store = getStore();
storeManager.removeSubStores(store, subStores);
// ... this also works
storeManager.removeSubStores(store, subStoreNames);
};
Reset store
You can reset the store to it's initial state with the reset
method.
// Suppose we have registered these stores dynamically in our app
const subStores = [subStore1, subStore2, subStore3];
// We can reset the store to it's initial state (will all the bootstrap sub-stores given)
export default () => {
const store = getStore();
storeManager.reset(store);
};
Dispatch
All substore actions
const dispatch = useDispatch();
dispatch.products.updateResource(id, 'rating', 5);
Substore actions
const dispatch = useDispatch('products');
dispatch.updateResource(id, 'rating', 5);
Selectors
useResourceSelector
Memoizes the selectors.
Redux-store uses useSelector()
with the default comparison strategy that is strict equality. The useResourceSelector
most of the time selects a portion of the state.
So, it works with shallowEqual strategy.
const rating = useResourceSelector('products', id, state => state.rating);
// does not rerender if `rating or loading` are not getting change
const {rating, state} = useResourceSelector('products', id, state => ({rating: state.rating, loading: state.loading});
useSubStoreSelector
Does not memoize, strict equality only
const ids = useSubStoreSelector('products', state => state.ids);
useSelector
Same as redux useSelector
const state = useSelector();
const loading = state.products.byId[id].loading;