4

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.

mcclosa
  • 943
  • 7
  • 29
  • 59
  • I'm not familiar with ``easy-peasy`` but in general, is it not the idea to keep one store that acts, essentially, as the central repository for all your application state? It is the one single source of truth. This is, precisely, to avoid having multiple holders of bits of state scattered throughout your application, which makes reasoning and predicting your application harder than it should be... My suggestion, use one store and put all your state in there... – Kibonge Murphy Feb 01 '21 at 18:39
  • That does sound like potentially the best solution at the moment. However, I was worried about any potential performance issues of having all of my application state and actions in the one place to be accessed anywhere. – mcclosa Feb 01 '21 at 19:05
  • 1
    Usually, it's a tradeoff between maintainability and this potential yet improbable performance issue. Unless your application really deals with huuuuuuuge amount of data, you shouldn't be worried. If it the case though or you have performance issues (e.g. too many frequent re-renders visibly slowing your ui), look into ``useMemo``, ``useCallback`` and the various other mechanisms that React provides to address performance issues. – Kibonge Murphy Feb 01 '21 at 19:17

1 Answers1

4

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.

JAM
  • 6,045
  • 1
  • 32
  • 48