40

An Alternative to React Redux by Using the React Hooks API

 5 years ago
source link: https://www.tuicool.com/articles/hit/QB3Ifuz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
6jMFn2v.png!web

Motivation

Everyone is exiciting with the new React Hooks API. So am I. Having been thinking how to manage global state, the Hooks API seems promising. By the way, I like Redux a lot, but I don’t like react-redux a.k.a connect very much. It is too complicated for beginners to use it properly. For example, reselect / memoization is a hard concept to explain. My recommendation is to structure a global state so that mapStateToProps only needs to select a part of the state without any logic. If you are really free to structure a global state, you can make it so that it selects direct properties of the state, which means “one-depth”.

My library

With that in mind, I’ve been developing a library for managing global state.

The global state in this library is an object which consists of items. For example:

const state = {
  name: 'foo',
  age: 23,
  hobbies: ['readig', 'videogaming'],
  scores: { stageA: 3, stageB: 7, stageC: 2 },
};

This state consists of 4 items (name, age, hobbies and scores). Each item has a value, which can be not only a primitive value but also an array or an object. You can connect to each item to get notified when a value is updated, but not a deep value in the object tree.

This library recently supports a reducer for updating states. So, you might expect it as a replacement for Redux for React. Maybe, maybe not. You will see it by yourselves.

Example code

You define a global state and export some methods like the following.

import { createGlobalState } from 'react-hooks-global-state';

type Action = {
  type: 'increment',
} | {
  type: 'decrement',
} | {
  type: 'setFirstName',
  firstName: string,
} | {
  type: 'setLastName',
  lastName: string,
} | {
  type: 'setAge',
  age: number,
};

const { dispatch, stateItemHooks } = createGlobalState(
  {
    counter: 0,
    person: {
      age: 0,
      firstName: '',
      lastName: '',
    },
  },
  (state, action: Action) => {
    switch (action.type) {
      case 'increment': return {
        ...state,
        counter: state.counter + 1,
      };
      case 'decrement': return {
        ...state,
        counter: state.counter - 1,
      };
      case 'setFirstName': return {
        ...state,
        person: {
          ...state.person,
          firstName: action.firstName,
        },
      };
      case 'setLastName': return {
        ...state,
        person: {
          ...state.person,
          lastName: action.lastName,
        },
      };
      case 'setAge': return {
        ...state,
        person: {
          ...state.person,
          age: action.age,
        },
      };
      default: return state;
    }
  },
);

export const {
  counter: useGlobalStateCounter,
  person: useGlobalStatePerson,
} = stateItemHooks;

export { dispatch };

Don’t be bothered by the Action type if you are not familiar with TypeScript. The first argument of createGlobalState is an initial global state. A reducer is passed to the second argument, which should look familiar if you have ever used Redux. You could use combineReducers if you like. Notice the hooks useGlobalStateCounter and useGlobalStatePerson is destructured from stateItemHooks , which is also for TypeScript (inferencing types). Finally, dispatch is exported (which is also typed).

The following is how to use the counter in a component.

import { dispatch, useGlobalStateCounter } from './state';

const increment = () => dispatch({ type: 'increment' });
const decrement = () => dispatch({ type: 'decrement' });

const Counter = () => {
  const [value] = useGlobalStateCounter();
  return (
    <div>
      <span>
        Count:
        {value}
      </span>
      <button type="button" onClick={increment}>+1</button>
      <button type="button" onClick={decrement}>-1</button>
    </div>
  );
};

This should be easy enough. The dispatch and useGlobalStateCounter is imported from the previous file. Two callbacks are defined and use dispatch . You might be wondering why we have dispatch somewhat globally. It certainly reduces the code isolation, but even if I were to use Context API, it would be almost the same pattern and I ended up with this pattern. Please correct me if I misunderstand something.

The rest of the code including the Person component can be found in the repository: https://github.com/dai-shi/react-hooks-global-state/tree/master/examples/06_reducer

You can run the example by:

git clone https://github.com/dai-shi/react-hooks-global-state.git
npm install
npm run examples:reducer

Then, open http://localhost:8080 in the browser.

Important Notes

React Hooks API is not yet released and this library is still an experiment at this point. Feedbacks are welcomed to improve the library. I wonder many people are trying the similar thing and I want to get ideas if possible.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK