Global state management under React Hooks

React Hooks

React Hooks are a new feature in React 16.7.0-Alpha that addresses the issue of sharing state logic between components.

  • **useState: ** Allows you to declare and change state in a function component. Previously, only class components could. (useImmer)
  • **useEffect: ** Allows the React lifecycle function to be used abstractly in function components. Developers can use more functional, clearer hooks.

Reconstructing Avatar components with local state using hooks:

import React, { useState, useEffect } from 'react';
const Avatar = (a)= > {
	// Create user state and modify state functions
	const [user, setUser] = useState("Ma Fei");
	/ / the default componentDidMount/componentDidUpdate triggered when the callback
	// The second argument can also be used to specify the firing time
	useEffect((a)= > {
		document.title = current user: ${user}; });// Use setUser to change the state
	return <div onClick={()= > setUser("mafeifei")}>{user};
};
Copy the code
  • useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code

An alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its corresponding dispatch method, providing a way to manage state within the component using Redux. Here is an example of a counter that overwrites the useState section with reducer:

const initialState = {count: 0};
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={()= > dispatch({type: 'increment'})}>+</button>
      <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
Copy the code

Hooks official documentation

Redux

Redux, currently the most popular state management framework, also provides support for Hooks in version 7.1.0.

API

The Store and Reducer parts are the same as the original, and the outer layer also needs to package the Provider

const store = createStore(rootReducer)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>.document.getElementById('root'))Copy the code

Redux provides a new API for Hooks on getting and updating store data.

useSelector()

const result : any = useSelector(selector : Function, equalityFn? : Function)
// useSelector(state => state.count)
Copy the code

The selector is conceptually similar to the mapStateToProps parameter of Connect, with state in the store as the first argument and called every time the Component renders. In terms of data updates, useSelector subscribes to the store, and when an action is executed it calls selector again to get the latest data. Unlike mapStateToProps, useSelector uses === to compare before and after data by default, so a second parameter equalityFn is provided to customize the comparison function.

useDispatch

const dispatch = useDispatch()
Copy the code

UseDiapatch returns a reference to dispatch to invoke the action.

useStore()

const store = useStore()
Copy the code

UseStore returns a reference to store in Redux. You can use store.getstate () to getState in store.

Demo

Reducer can use CodesandBox with IMmer

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";
import {produce} from 'immer'

const initState = {
  count: 0
};

const reducer = (state = initState, action) = > produce(state, draft => {
  switch (action.type) {
    case "increase": {
      draft.count += 1
      break
    }
    case "decrease": {
      draft.count += 1
      break
    }
    default:}})const store = createStore(reducer);

const Counter = (a)= > {
  const count = useSelector(state= > state.count); // useSelector
  const dispatch = useDispatch(); // useDispatch
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={()= > dispatch({ type: "increase" })}>Increase</button>
      <button onClick={()= > dispatch({ type: "decrease" })}>Decrease</button>
    </div>)}const App = (a)= > {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>

  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />,
  rootElement
);

Copy the code


New Context API + Hooks

useContext

React Hooks provide useContext to access the context and use Consumer without wrapping Children

import React, { useContext } from 'react';

function Display() {
  const value = useContext(NumberContext);
  return <div>The answer is {value}.</div>;
}
Copy the code

useReducer

UseReducer is an API provided by hooks to replace useState state management for complex scenarios. It provides actions and reducer methods to manage state within components. It receives a reducer of the form (state, action) => newState, And returns the current state and its accompanying Dispatch method.

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={()= > dispatch({type: 'increment'})}>+</button>
      <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
Copy the code

This article provides a way to implement global state management using context and hooks. Compared with context, the useReducer with hooks provides more powerful state management capabilities.

import React, {createContext, useContext, useReducer} from 'react';
export const StateContext = createContext();

export const StateProvider = ({reducer, initialState, children}) = >(
  <StateContext.Provider value={useReducer(reducer, initialState)} >
    {children}
  </StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
Copy the code

Create a StateContext, and then set the context value to useReducer(Reducer, initialState) in the Provider of StateContext. After the reducer is called, The value of the context will also change, as if referencing the context is worth updating. The useReducer also ensures that components are updated only when data changes. Access to context data is also wrapped in a layer of useStateValue hooks that do not need to call useContext on every component.

demo

codesandbox

import React, { createContext, useContext, useReducer } from "react";
import { render } from "react-dom";
import { produce } from "immer";

const StateContext = createContext();

const StateProvider = ({ reducer, initialState, children }) = > (
  <StateContext.Provider value={useReducer(reducer, initialState)} >
    {children}
  </StateContext.Provider>
);
const useStateValue = () => useContext(StateContext);

function App() {
  const initialState = {
    count: 0
  };
  const reducer = (state = initialState, action) =>
    produce(state, draft => {
      switch (action.type) {
        case "increase": {
          draft.count += 1;
          break;
        }
        case "decrease": {
          draft.count += 1;
          break;
        }
        default:
      }
    });
  return (
    <StateProvider initialState={initialState} reducer={reducer}>
      <Count />
    </StateProvider>
  );
}

const Count = () => {
  const [{ count }, dispatch] = useStateValue();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={()= > dispatch({ type: "increase" })}>Increase</button>
      <button onClick={()= > dispatch({ type: "decrease" })}>Decrease</button>
    </div>
  );
};

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Copy the code


unstated-next

Unstated is a lightweight state management tool that is popular in the React community. It adopts the design idea of React in API design and can be quickly understood and mastered. Unstated throws three objects, namely Container, Subscribe, and Provider. Unstated would use React. CreateContext to create a StateContext object for state transfer. unstated demo

import React from 'react';
import { render } from 'react-dom';
import { Provider, Subscribe, Container } from 'unstated';

type CounterState = {
  count: number
};

class CounterContainer extends Container<CounterState> {
  state = {
    count: 0
  };

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  decrement() {
    this.setState({ count: this.state.count - 1}); }}function Counter() {
  return (
    <Subscribe to={[CounterContainer]}>
      {counter => (
        <div>
          <button onClick={()= > counter.decrement()}>-</button>
          <span>{counter.state.count}</span>
          <button onClick={()= > counter.increment()}>+</button>
        </div>
      )}
    </Subscribe>
  );
}

render(
  <Provider>
    <Counter />
  </Provider>.document.getElementById('root'));Copy the code

With the introduction of Hooks, Unstate also launched a new generation of state management library, Unststation-Next, which was consistent with the Hooks API. In terms of API design, unstate continued to be similar to the React operation, using custom Hooks to manage state.

createContainer(useHook)

Create a container that accepts a custom hook argument that encapsulates the state

import { createContainer } from "unstated-next"
function useCustomHook() {
  let [value, setValue] = useState()
  let onChange = e= > setValue(e.currentTarget.value)
  return { value, onChange }
}
let Container = createContainer(useCustomHook)
Copy the code

<Container.Provider initialState>

The Provider in unstated was retained

function ParentComponent() {
  return (
    <Container.Provider initialState={"value"} >
      <ChildComponent />
    </Container.Provider>)}Copy the code

useContainer(Container)

Get the state and actions in a custom hook from within the component via useContainer

import { useContainer } from "unstated-next"
function ChildComponent() {
  let input = useContainer(Container)
  return <input value={input.value} onChange={input.onChange} />
}
Copy the code

Demo

codesandbox

import { render } from "react-dom";
import React, { useState } from "react";
import { createContainer } from "unstated-next";

function useCounter(initialState = 0) {
  let [count, setCount] = useState(initialState);
  let increase = (a)= > setCount(count + 1);
  let decrease = (a)= > setCount(count - 1);
  return { count, increase, decrease };
}
let Counter = createContainer(useCounter);

function App() {
  let counter = Counter.useContainer();
  return (
    <div>
      <h1>Counter: {counter.count}</h1>
      <button onClick={counter.increase}>Increase</button>
      <button onClick={counter.decrease}>Decrease</button>
    </div>
  );
}

render(
  <Counter.Provider>
    <App />
  </Counter.Provider>,
  document.getElementById("root")
);

Copy the code

mobx-react-lite

Mox-react-lite, a state management component less popular than Redux, matches hooks, no longer inject store into Component, Instead, it works with react’s new Context API to put a store into context management.

demo

codesandbox

import { observable } from 'mobx' import { Observer, useObserver, Observer} from 'mobx-react' // 6.x or [email protected] import ReactDOM from 'react-dom' const person = observable({ name: 'John', }) // named function is optional (for debugging purposes) const P1 = observer(function P1({ person }) { return <h1>{person.name}</h1> }) const P2 = ({ person }) => <Observer>{() => <h1>{person.name}</h1>}</Observer> const P3 = ({ person }) => { return useObserver(() => <h1>{person.name}</h1>) } ReactDOM.render( <div> <P1 person={person} /> <P2 person={person} /> <P3 person={person} /> </div>, ) setTimeout(() => { person.name = 'Jane' }, 1000)Copy the code

To compare

  • Redux is the most popular state management tool in the community. It has a good ecosystem, supports Middleware, and provides predictable state management, but requires a lot of template code to write, and the package size is relatively large among state management tools for large projects
  • Context + hooks provides basic state management. In conjunction with the hooks useReducer, redux supports most of the functions provided by redux without introducing additional libraries
  • The advantage of unstated encapsulating states and operations in containers is that it is more similar to the react operation mode than Redux in style and easier to use. It only costs 200B. However, it has no advantage over context+useReducer unless an unstated project has been used. Otherwise, it is not recommended.
  • Mobx-react-lite continues the advantages of MOBx. It manages the state in the observable object, adopts the proxy method, and supports the direct operation of the object. Compared with Redux, the code writing method is simpler, but for large projects, Mobx’s more flexible approach is not as clear as the reducer Action -> Reducer ->state process, which is suitable for small and medium-sized projects.