easy-peasy
easy-peasy copied to clipboard
Accessing multiple stores from single component
I'm trying to access 2 different stores in a single component, but worry that perhaps the architecture of my app may need to change as easy-peasy
may not have this functionality.
I have a GlobalStore
import { createStore } from 'easy-peasy';
const globalModel = {
menuOpen: false,
toggleMenu: action((state, payload) => {
state.menuOpen = payload;
}),
};
const GlobalStore = createStore(globalModel);
export default GlobalStore;
Just for this example, I'll use a single state and action used in the store to define whether the navigation menu is open or not.
The GlobalStore appears at the top level of my app in my App.js
file.
import React from 'react';
import { StoreProvider } from 'easy-peasy';
import GlobalStore from './store/GlobalStore';
const App = () => {
return (
<StoreProvider store={GlobalStore}>
</StoreProvider>
);
};
export default App;
Now, further down the tree, I have another store SearchStore
that dictates which view is active in the component.
import { createStore } from 'easy-peasy';
import { action } from 'easy-peasy';
const searchModel = {
view: 'filter',
setView: action((state, payload) => {
state.view = payload;
}),
};
const SearchStore = createStore(searchModel);
export default SearchStore;
The issue I have now is that in a component that I need to be able to access both stores to update the view with the setView
action in the SearchStore
and get the value of menuOpen
from the GlobalStore
but cannot access both concurrently.
The example I have in a component is that I have a styled component that when clicked calls the action setView
but its position is also defined by whether the menuOpen
is true
or not. but obviously, if I try and get the state of menuOpen
it will be undefined as it does not exist in SearchStore
const Close = styled.span`
$(({ menuOpen }) => menuOpen ? `
// styles go here
` : `` }
`;
const setView = useStoreActions((action) => action.setView);
const menuOpen = useStoreState((state) => state.menuOpen);
<Close menuOpen={menuOpen} onClick={() => setView('list')}>
Is this possible? Any help would be much appreciated.
By no means is this post an official solution, but I wanted to try to help nonetheless.
How I have managed global and subset states is to create a nested store.
import globalModel from './globalModel';
import searchModel from './searchModel';
const store = createStore({
...globalModel,
search: searchMode,
});
// CloseIcon
const setView = useStoreActions((actions) => actions.search.setView);
const menuOpen = useStoreState((state) => state.menuOpen);
I have done this nested store approach several times and have found it immensely convenient. I've had a similar store setup like this before:
const store = createStore({
settings: settingsModel, // global-like values like language and theme or menuOpen in your case
products: productsModel,
cart: cartModel,
...
But if that's not what you're looking for, something else worth considering is store-specific hooks.
import { createTypedHooks } from 'easy-peasy';
const typedGlobalHooks = createTypedHooks<GlobalModel>();
export const useGlobalActions = typedGlobalHooks.useStoreActions;
export const useGlobalState = typedGlobalHooks.useStoreState;
const typedSearchHooks = createTypedHooks<SearchModel>();
export const useSearchActions = typedSearchHooks.useStoreActions;
export const useSearchState = typedSearchHooks.useStoreState;
// CloseIcon
import { useSearchActions, useGlobalState } from './store';
const setView = useSearchActions(actions => actions.setView);
const menuOpen = useGlobalState(state => state.menuOpen);
I'm not sure how this approach works without TypeScript, however.
I hope this helps :)
You can put multiple models in a store like this:
import { createStore } from 'easy-peasy';
const menuModel = {
menuOpen: false,
toggleMenu: action((state, payload) => {
state.menuOpen = payload;
}),
};
const searchModel = {
view: 'filter',
setView: action((state, payload) => {
state.view = payload;
}),
};
const globalModel = {
menu: menuModel,
search: searchModel,
}
const GlobalStore = createStore(globalModel);
export default GlobalStore;
or you can create multiple stores using createContextStore API
Thanks, @yuschick & @duygiangdg these approaches have helped.
I guess my next question is that I currently have my StoreProvider
at the top level of my application, wrapping over all my components. Adding an inner StoreProvider
in one my components, overrides my global store, which I do need to access on all of my routes.
Say I have a route, I have a store model specific for it, that page has and id param in the URL and the store state gets updated with data based on that id, if I were to navigate away from that route and select the same route again with a different id in the URL param, the previous store state will display until it's overwritten.
Which means, on the unmount of each route that updates the store in any way I have to set the state back to default? That way you don't see the previously accessed id data on a route a user has accessed before.
I can maybe do a CodeSanbox later to detail this a bit better.
Is there a better way around this? That would allow me to do something like the following or any way I don't have to remember to update the state on the unmount of certain components.
<StoreProvider store={globalStore}>
<SomeComponent />
<OtherComponent>
<StoreProvider store={otherStore}>
<ComponentUsingOtherStore>
/*
Can access global store but also otherStore
other store native to ComponentUsingOtherStore component
meaning navigating away from screen removes store values
so state is clean when entering ComponentUsingOtherStore again
*/
</ComponentUsingOtherStore>
</StoreProvider>
</OtherComponent>
</StoreProvider>
I think the answer to this is to use the createContextStore
api. That will allow you to scope things in a very fine grained way, while creating separate contexts that don't clash.