1.10.1 • Published 3 years ago

hookstores v1.10.1

Weekly downloads
524
License
MIT
Repository
github
Last release
3 years ago

⛵ hookstores

Hookstores is an elementary Flux implementation using React hooks and Immer.

action->dispatcher->store->view

📦 installation

> npm i --save hookstores

Then wrap your React app with the context Provider:

import React from 'react';
import {render} from 'react-dom';
import {Hookstores} from 'hookstores';

render(
  <Hookstores>
    <App />
  </Hookstores>,
  container
);

🎨 idea

  • Hookstores allows to organize your React app State in one or many stores.
  • Access local parts of this global state with hooks
  • You can listen to specific parts of specific stores, to allow accurate local rendering from your global app state (see the advanced section).
const ItemsContainer = props => {
  const {useItemsStore} = useHookstores();
  const {items} = useItemsStore();

  return <ItemsComponent items={items} />;
};

🛠 setup

Here is the path to follow to setup Hookstores on your app:

  • 1: write reactions: the way your store must react to actions.
  • 2: give your stores to <Hookstores>
  • 3: read stores data in your containers
  • 4: dispatch actions to trigger reactions.

In the following, let's illustrate how to use Hookstores with:

  • a store of Items and its fetch function
  • a container plugged to this store,
  • and the component rendering the list of items.

Illustration will be marked with 🔍

📦 create a store

You'll have to define a store with

  • an initialState,
  • and a list of reactions.
const itemsStore = {
  initialState: {},
  reactions: []
};

export default itemsStore;

You should use one store for each feature. (🔍 here the itemsStore to deal with the Items)

Hookstores will create the actual store for each of them to:

  • handle an immutable state with Immer,
  • listen to actions to trigger the appropriate reactions,
  • emit updates to the containers when there are changes only.

computeAction

🔆 Reactions

Here is a reaction:

const doSomethingInThisStore = {
  on: 'ACTION_TYPE',
  reduce: (state, payload) => {
    /*
      Just update the state with your payload.
      Here, `state` is the draftState used by `Immer.produce`
      You store will then record your next immutable state.
    */
    state.foo = 'bar';
  },
  perform: (parameters, getState, dispatch) => {
    /*
      Optional sync or async function.
      It will be called before `reduce`

      When it is done, reduce will receive the result in
      the `payload` parameter.

      You can `dispatch` next steps from here as well
    */
  }
};
  • A reaction will be triggered when an action is dispatched with action.type === on.

  • reduce is called using Immer, so mutate the state exactly as you would with the draftState parameter in produce.

  • If you have some business to do before reducing, for example calling an API, use the perform function, either sync or async.

    Then reduce will be called with the result once it's done.

Here is the example for our illustrating itemsStore

/* ./features/items/store.js */
import apiCall from './fetch-items.js';

const FETCH_ITEMS = 'FETCH_ITEMS';

const initialState = {items: null};

const fetchItems = {
  on: FETCH_ITEMS,
  perform: async (parameters, getState) => {
    // This function will be called whenever {type:FETCH_ITEMS} is dispatched.
    // `getState` is provided here for convenience, to access the current store state.

    const items = await apiCall(parameters);
    return items;
  },
  reduce: (draft, payload) => {
    // 'reduce' will be called after `perform` is over.
    // 'perform' returns the items, so here payload === items
    draft.items = payload;
  }
};

const reactions = [fetchItems];

export default {initialState, reactions};
export {FETCH_ITEMS};

🏁 setup the Hookstores provider with these stores

Once all stores are ready, and pass them as stores parameter to <Hookstores>.

This is where you define names your stores.

Hookstores will simply create hooks with the same names with the use prefix.

whatheverNameStore ===> useWhatheverNameStore()

🔍 Example:

/* ./index.js */
import React from 'react';
import {render} from 'react-dom';
import {Hookstores} from 'hookstores';

import itemsStore from './features/items/store.js';
import anyOtherStore from './features/whatever/store.js';

const stores = {
  itemsStore,
  anyOtherStore
};

render(
  <Hookstores stores={stores}>
    <App />
  </Hookstores>,
  container
);

here Hookstores will create those hooks:

const {useItemsStore, useAnyOtherStore} = useHookstores();

🍕 Using those stores in your containers

storeState ➡️ props

Listen to changes in a store and use in your local props by using the useXxxxStore hook that was created for your store.

🔍 Here is the example for our illustrating itemsStore:

/* ./features/items/container.js */

import React from 'react';
import ItemsComponent from './component';

const ItemsContainer = props => {
  const {useItemsStore} = useHookstores();
  const {items} = useItemsStore();

  return <ItemsComponent items={items} />;
};

To listen to specific changes in a store, and update your local props only on those changes, use propsMapping (see the advanced section).

📡 dispatching actions

Use prop drilling from your containers to your components: pass functions dispatching the actions

import {SELECT_ITEM} from './features/items/store.js';

const ItemsContainer = props => {
  const {dispatch} = useHookstores();

  const selectItem = id => () => {
    dispatch({
      type: SELECT_ITEM,
      itemId: id
    });
  };

  return <ItemsComponent selectItem={selectItem} />;
};

🔥 advanced usage

The whole point of Hookstores is to be able to perform extremely local rendering.

So, rather than the listening for the whole state updates, you can update rendering depending on specific updates in a store.

To do so, specify the props mapping you want to listen for changes, telling corresponding paths in your store.

const propsMapping = {
  items: 'path.to.items.within.your.store',
  other: 'plop'
};

Now your props will change only when one of these mapping is updated in the store.

const ItemsContainer = props => {
  const {useItemsStore} = useHookstores();
  const {items, other} = useItemsStore(propsMapping);

  return <ItemsComponent items={items} other={other} />;
};

This way, on every store update, specific props will be extracted for the components, and nothing else: this will allow accurate local rendering from a global app state.

📚 motivation

read this doc

🏗️ development

peerDeps devDeps deps

local dev tips

1.10.1

3 years ago

1.10.0

3 years ago

1.9.1

3 years ago

1.9.0

3 years ago

1.8.1

3 years ago

1.8.0

3 years ago

1.7.3

3 years ago

1.7.2

3 years ago

1.7.1

3 years ago

1.7.0

3 years ago

1.7.5

3 years ago

1.7.4

3 years ago

1.5.5

3 years ago

1.5.4

3 years ago

1.5.3

3 years ago

1.5.2

3 years ago

1.6.0

3 years ago

1.5.1

3 years ago

1.5.0

3 years ago

1.4.1

3 years ago

1.4.0

3 years ago

1.3.0

3 years ago

1.2.1

3 years ago

1.2.0

3 years ago

1.1.3

3 years ago

1.1.1

3 years ago