Accessing multiple easy-peasy stores from single component

1.3k Views Asked by At

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.

1

There are 1 best solutions below

2
On

Alternative 1: extending the global store

To access both store (via the useStoreState/Actions from the StoreProvider), you could nest both "sub" stores into the GlobalStore:

// SearchModel.js
import { action } from 'easy-peasy';

const searchModel = {
  view: 'filter',
  setView: action((state, payload) => {
    state.view = payload;
  }),
};

export default searchModel;
// MenuModel.js
import { action } from 'easy-peasy';

const menuModel = {
  isOpen: false,
  toggle: action((state, payload) => {
    state.isOpen = !state.isOpen;
  }),
};

export default menuModel;
// GlobalStore.js
import { createStore } from 'easy-peasy';

import menu from './MenuhModel';
import search from './SearchModel';

const globalModel = {
  menu,
  search,
};

const GlobalStore = createStore(globalModel);
export default GlobalStore;

This way, you can access both stores at your convenience, using the hooks:

const searchState = useStoreState((state) => state.search);
const menuState = useStoreState((state) => state.menu);

const searchActions = useStoreActions((action) => action.search);
const menuActions = useStoreActions((action) => action.menu);

Alternative 2: useLocalStore()

If you do not want to extend the global store, you could create a local store, by using the useLocalStore():

function Menu() {
  const [state, actions] = useLocalStore(() => ({
    isOpen: false,
    toggle: action((state, payload) => {
      state.isOpen = !state.isOpen;
    }),
  }));

  return (
    <div>
      {state.isOpen && <MenuItems />}
      <button onClick={() => actions.toggle()}>Open menu</button>
    </div>
  );
}

However, the drawback of this approach, is that the state is not global and only available at the component-level.

You could however get around this, by creating your own provider - but then again, alternative 1 would probably be the path of least resistance.