1.1.0 • Published 5 years ago

@uralys/hookstores v1.1.0

Weekly downloads
-
License
-
Repository
github
Last release
5 years ago

⛵ hookstores

Hookstores is an elementary Flux implementation using React hooks.

action->dispatcher->store->view


šŸ“š motivation

  • React is no more a View lib, it's now (v17) a complete framework: so either we pick a lighter lib for the View, or choosing React āŒ we shouldn't need to use an additional external framework such as Redux, MobX, RxJs, Recoil, Jotail...

  • A first approach could be to use local states and sporadic use of React context, like explained here by Kent C. Dodds, but āŒ it's not a proper Flux implementation, I'd rather have my entire app state fully separated from the View, and "connect" containers, mapping sub-states to the views, the way Redux allows to.

  • Using React context for a global app state, like suggested here by Rico Sta. Cruz, or here by Ebenezer Don, would be ok for a small application, but would quickly lead to āŒ tons of useless re-renderings. That would eventually lead to lots of specific useMemo on every component requiring performance optimisation. So rather than to put the effort on developping on a proper state/component architecture, your effort will be spent on āŒ writing those useMemo everywhere.

šŸ§™ experimentation

The idea with hookstores is to implement a simple Flux architecture

  • āœ… splitting the the global state into stores,
  • āœ… applying local rendering, by mapping these stores to containers, using React hooks useState and useEffect.
  • āœ… using React context only to provide the Dispatcher everywhere, and StoresProvider who is emitting events to listeners of specific stores only.

ā˜¢ļø disclaimer

So yes, somehow it ends up as another lib to manage your React state šŸ™ƒ.

But since it's only few files you should understand what's behind the hood, then use and tweak them to your convenience within your own React app rather than use it out of the box.

Furthermore,

  • āš ļø it's not written in typescript šŸ™€
  • āš ļø there are no tests šŸ’„

That being said,

  • āœ… I'm confidently using this implementation between many apps,
  • āœ… so I prefer to have this package,
  • āœ… so why not sharing this experiment.

šŸ“¦ installation

> npm i --save @uralys/hookstores

setup

use Dispatcher and StoresProvider context providers to compose your root <App/>

import {Dispatcher, StoresProvider} from '@uralys/hookstores';
<Dispatcher>
  <StoresProvider>
    <App />
  </StoresProvider>
</Dispatcher>

usage

In the following, let's illustrate how to use hookstores with a store of Items, with a fetch function, a container plugged to this store, and the component rendering the list of items.


prepare descriptions

Describe all your stores:

  • you should use one store for one feature (here the items)
  • define within computeAction how a store must update its state for every handledAction:
/* ./features/items/store-description.js */
const FETCH_ITEMS = 'FETCH_ITEMS';

const computeAction = async (currentState, action) => {
  let newState;

  switch (action.type) {
    case FETCH_ITEMS: {
      const items = await fetchItems();
      newState = {...currentState, items};
      break;
    }
    default:
      newState = {...currentState};
  }

  return newState;
};

const itemsStoreDescription = {
  name: 'itemsStore',
  initialState: {items: null},
  handledActions: [FETCH_ITEMS],
  computeAction
};

export default itemsStoreDescription;
export {FETCH_ITEMS};

create stores

1 - First thing the <App> has to do is to instanciate all stores.

They will be registered and will listen to all dispatched actions through the Dispatcher.

2 - Compose your containers with every store you need

Then, everytime they compute an action and update their state, they notify all connected containers.

import {useStores, withStore} from '@uralys/hookstores';
import itemsStoreDescription from './features/items/store-description';

const storesDescriptions = [itemsStoreDescription];
const {createStores} = useStores();

const App = () => {
  const {createStores} = useStores();
  createStores(storesDescriptions);

  const ItemsWithStores = withStore(itemsStoreDescription)(ItemsContainer);

  return <ItemsWithStores />;
};

apply state changes to components props

use connectStore to register to store changes on component mounting.

import React, {useLayoutEffect, useState} from 'react';
import ItemsComponent from './component';

const ItemsContainer = props => {
  const [items, setItems] = useState();
  const {itemsStore} = props;

  useLayoutEffect(() => {
    const onStoreUpdate = storeState => {
      setItems(storeState.items);
    };

    const disconnect = connectStore(itemsStore, onStoreUpdate);

    return disconnect;
  }, []);

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

šŸ” don't forget to return the disconnect function at the end of your hook, unless you may have stores updates triggering unmounted containers updates.


dispatch actions from containers

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

import {SELECT_ITEM} from 'path/to/actions';

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

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

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

šŸ—ļø development

local dev tips

1.1.0

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago