createContextStore

Creates a store powered by context, allowing you to expose state to specific parts of your React application.

Using this approach over the global createStore approach allows you to create multiple stores. Each store can encapsulate differing state needs for branches/features of your application. This may be especially useful for larger scale applications, or when employing code splitting techniques.

Tutorial

This section will provide you with step by step instructions on how to create and consume a context store within your application.

It will not exhaustively cover how to create your model or utilise all of the hooks. Please reference the respective docs to gain a deeper understanding of those APIs.

Creating a context store

Firstly create your context store, defining it's model:

import { createContextStore } from 'easy-peasy';

const CounterStore = createContextStore({
  count: 0,
  increment: action((state) => {
    state.count += 1;
  }),
});

export default CounterStore;

Binding to your application

In order to use the store within your application you firstly need to identify the components within your application that you wish to have access to the store.

Once you have done this wrap the component(s) with the store's Provider.

import CounterStore from './stores/counter';

function MyApp() {
  return (
    <>
      <Header />
      {/* πŸ‘‡ exposing store to our Main component */}
      <CounterStore.Provider>
        <Main />
      </CounterStore.Provider>
      <Footer />
    </>
  );
}

Consuming state from the store

Any components that have been wrapped with the store's Provider will be able to utilise the store's useStoreState hook to access state.

import CounterStore from './stores/counter';

function Main() {
  // Access the store's state via the hook
  //                              πŸ‘‡
  const count = CounterStore.useStoreState((state) => state.count);

  return <main>Current count is: {count}</main>;
}

###Β Dispatching actions from the store

Any components that have been wrapped with the store's Provider will be able to utilise the store's useStoreActions hook to access actions.

import CounterStore from './stores/counter';

function Main() {
  const count = CounterStore.useStoreState((state) => state.count);
  // Access the store's actions via the hook
  //                                  πŸ‘‡
  const increment = CounterStore.useStoreActions(
    (actions) => actions.increment,
  );

  return (
    <main>
      Current count is: {count}
      {/* Dispatch the action   πŸ‘‡  */}
      <button onClick={() => increment()}>+</button>
    </main>
  );
}

Defining injections at runtime

It is possible to provide the store injections at runtime. In order to do so you can provide them the store's Provider.

import CounterStore from './stores/counter';

function MyApp({ language }) {
  // Imagine we had a value that changes at runtime, which we intend to use
  // as an injection into our application
  const translator = useTranslator(language);

  return (
    <>
      <Header />
      {/* pass down the injections into the provider
                                               πŸ‘‡  */}
      <CounterStore.Provider injections={{ translator }}>
        <Main />
      </CounterStore.Provider>
      <Footer />
    </>
  );
}

This will override any previous injections that were defined when creating the store. If you wish to instead update the existing injections you can utilise the function form.

import CounterStore from './stores/counter';

function MyApp({ language }) {
  // Imagine we had a value that changes at runtime, which we intend to use
  // as an injection into our application
  const translator = useTranslator(language);

  return (
    <>
      <Header />
      <CounterStore.Provider
        {/* Pass down a function into the injections.
            It receives the previous injections.
                          πŸ‘‡ */}
        injections={(previousInjections) => ({
          // Spread the existing inject values so we can keep them:
          ...previousInjections,
          // Then overwrite the "translator":
          translator,
        })}
      >
        <Main />
      </CounterStore.Provider>
      <Footer />
    </>
  );
}

Customising your model at runtime

This example shows how you can use the runtimeModel prop of your context store's Provider in order to customise your model at runtime.

// Use the function form of defining your model. The function will receive the
// runtime model data that was provided as a prop to the store's Provider
//                                      πŸ‘‡
const Counter = createContextStore((runtimeModel) => ({
  count: runtimeModel.count,
  inc: action((state) => {
    state.count += 1;
  }),
}));

ReactDOM.render(
  // Provide the runtime model overrides/customization as a prop
  //                                 πŸ‘‡
  <Counter.Provider runtimeModel={{ count: 1 }}>
    <CounterApp />
  </Counter.Provider>,
  document.querySelector('#app'),
);

If you needed you could even make the runtimeModel define the entire model.

// We will just return the runtime model that we expect to receive via the
// store's Provider
//                                      πŸ‘‡
const Counter = createContextStore((runtimeModel) => runtimeModel);

ReactDOM.render(
  <Counter.Provider
    // Provide the runtime model as a prop
    //    πŸ‘‡
    runtimeModel={{
      count: runtimeModel.count,
      inc: action((state) => {
        state.count += 1;
      }),
    }}
  >
    <CounterApp />
  </Counter.Provider>,
  document.querySelector('#app'),
);

Note: this will only be used to initialize the model on the first render. If you wish to create a whole new runtime model you need to ensure that you recreate Provider completely.

API

This function can be imported like so:

import { createContextStore } from 'easy-peasy';

Arguments

The following arguments are accepted:

  • model (Object | (runtimeModel: any) => Object, required)

    The model representing your store.

  • config (Object, optional)

    Custom configuration for your store. Please see the StoreConfig API documentation for a full list of configuration options.

Returns

When executed you will receive a store container that contains the following properties:

  • Provider (Component)

    The React component that allows you to wrap a specific part of your React app in order to expose the store state to it. You can wrap as much or as little of your React app as you like.

    If you render multiple instances of this provider component each instance will have it's own unique state. This may be handy in some cases, but in most cases you will likely only have one instance of your provider rendered.

    <Counter.Provider>
      <App />
    </Counter.Provider>
    

    The provider accepts the following props:

    • runtimeModel (Any, not required)

      Allows you to provide runtime overrides for the store's model. This needs to be used in conjunction with the function form of defining your model.

      <Counter.Provider
        runtimeModel={{
          count: 1,
          inc: action((state, payload) => {
            state.count += 1;
          }),
        }}
      >
        <App />
      </Counter.Provider>
      
    • injections (Object || (previousInjections) => Object, optional)

      Allows you to provide additional data used to initialise your store's model. This needs to be used in conjunction with the function form of defining your model.

      <Counter.Provider injections={{ translator }}>
        <App />
      </Counter.Provider>
      

      or

      <Counter.Provider
        injections={(previousInjections) => ({
          ...previousInjections,
          translator,
        })}
      >
        <App />
      </Counter.Provider>
      
  • useStoreState (Function)

    A hook allowing you to access the state of your store.

    function CountDisplay() {
      const count = Counter.useStoreState((state) => state.count);
      return <div>{count}</div>;
    }
    

    This hook shares all the same properties and features of the global useStoreState hook.

  • useStoreActions (Function)

    A hook allowing you to access the actions of your store.

    function CountIncButton() {
      const increment = Counter.useStoreActions((actions) => actions.increment);
      return <button onClick={increment}>+</button>;
    }
    

    This hook shares all the same properties and features of the global useStoreActions hook.

  • useStoreDispatch (Function)

    A hook allowing you to access the dispatch of your store.

    function CountIncButton() {
      const dispatch = Counter.useStoreDispatch();
      return <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>;
    }
    

    This hook shares all the same properties and features of the global useStoreDispatch hook.

  • useStoreRehydrated (Function)

    A hook allowing you to access the rehydration status of the store. Only useful when utilising persist within your model.

    function App() {
      const rehydrated = Counter.useStoreRehydrated();
      return rehydrated ? <div>My App</div> : <div>Loading...</div>;
    }
    

    This hook shares all the same properties and features of the global useStoreRehydrated hook.

  • useStore (Function)

    A hook allowing you to access the store. We recommend that this only be used within exceptional use cases.

    function MyCounter() {
      const store = Counter.useStore();
      store.getState();
      return null;
    }
    

    This hook shares all the same properties and features of the global useStore hook.