Translate – react native tutorial 13 – react hook state management – no Redux and context required

Time:2020-9-26

Previous 12 tutorials

Today, we’ll explore it and develop a custom hook to manage global state — an easier way to use than Redux and more powerful than the context API.

Translate - react native tutorial 13 - react hook state management - no Redux and context required

Basic knowledge of hook

If you are already familiar with react hooks, you can skip this section.

useState()

Before hooks, functional components had no state. Now, with “usestate ()”, we can do that.

Translate - react native tutorial 13 - react hook state management - no Redux and context required

It works by returning an array. The first item in the array above is a variable that provides access to state values. The second is a function that updates the state of the component to reflect the new value on the dom.

useEffect()

Class components use a lifecycle approach to manage side effects, such ascomponentDidMount()useEffect()Functions allow you to perform side effects in function components.

By default, the effect runs after each rendering. However, you can choose to start it only when certain values change, passing an array of variables as the second optional parameter.

In order to get peacecomponentDidMount()As a result, we can send an empty array. Knowing that an empty array never changes, so the effect only runs once.

As we can see, hooks state works exactly like class component state. Each instance of a component has its own state.

To work on a solution that shares state between components, we will create a custom hook.

Translate - react native tutorial 13 - react hook state management - no Redux and context required

The idea is to create an array of listeners and a unique state object. Whenever a component changes state, all subscribed components are triggeredsetState()Function and get updated.

We can call it in our custom hookuseState()To achieve. But we will not returnsetState()Function, which is added to an array of listeners and returns a function that updates the state object and runs all the listener functions.

Wait, isn’t this supposed to make my life easier?

Yes, I created an NPM package that encapsulates all of this logic.

use-global-hook

In the range of less than 1KB, the hook is used for simple state management of react.

www.npmjs.com

You don’t need to override this custom hook in every project. If you just want to skip and use the final solution, you can easily add it to your project by running the following command.

npm install -s use-global-hook。

You can learn how to use it through the examples in the package documentation. But from now on, we’ll focus on how it works.

first edition


import { useState, useEffect } from 'react';

let listeners = [];
let state = { counter: 0 };

const setState = (newState) => {
  state = { ...state, ...newState };
  listeners.forEach((listener) => {
    listener(state);
  });
};

const useCustom = () => {
  const newListener = useState()[1];
  useEffect(() => {
    listeners.push(newListener);
  }, []);
  return [state, setState];
};

export default useCustom;

The first version is ready to share state. You can add as many counter components to your application, and it will have the same global state.

import React from 'react';
import useCustom from './customHook';

const Counter = () => {
  const [globalState, setGlobalState] = useCustom();

  const add1Global = () => {
    const newCounterValue = globalState.counter + 1;
    setGlobalState({ counter: newCounterValue });
  };

  return (
    <div>
      <p>
        counter:
        {globalState.counter}
      </p>
      <button type="button" onClick={add1Global}>
        +1 to global
      </button>
    </div>
  );
};

export default Counter;

But we can do better. What I didn’t like in the first version.

  • I want to remove the listener from the array when the component is unloaded.
  • I want to make it more generic so that we can use it in other projects.
  • I want to set an “initialstate” with parameters.
  • I want to use more function oriented programming.

Call a function before the component is unloaded.

We learned that calling “useeffect (function, ()) with an empty array has the same purpose as” componentdidmount() “. However, if the function used in the first parameter returns another function, the second function will be started before the component is unloaded. andcomponentWillUnmount()Exactly the same.

This is the best place to remove components from the listener array.

Second Edition

const useCustom = () => {
  const newListener = useState()[1];
  useEffect(() => {
    // Called just after component mount
    listeners.push(newListener);
    return () => {
      // Called just before the component unmount
      listeners = listeners.filter(listener => listener !== newListener);
    };
  }, []);
  return [state, setState];
};

In addition to this final modification, we will.

  • Set react as a parameter and no longer import it.
  • Do not export customhook, but export a function according toinitialStateParameter returns a new customhook.
  • Create a “store” object with a “state” value and a “setstate()” function.
  • replacesetState()anduseCustom()The arrow function in is a regular function, so we can have a context to set thestoreBind tothis
function setState(newState) {
  this.state = { ...this.state, ...newState };
  this.listeners.forEach((listener) => {
    listener(this.state);
  });
}

function useCustom(React) {
  const newListener = React.useState()[1];
  React.useEffect(() => {
    // Called just after component mount
    this.listeners.push(newListener);
    return () => {
      // Called just before the component unmount
      this.listeners = this.listeners.filter(listener => listener !== newListener);
    };
  }, []);
  return [this.state, this.setState];
}

const useGlobalHook = (React, initialState) => {
  const store = { state: initialState, listeners: [] };
  store.setState = setState.bind(store);
  return useCustom.bind(store, React);
};

export default useGlobalHook;

Because we now have a more general hook, we need to set it in the store file.

import React from 'react';
import useGlobalHook from './useGlobalHook';

const initialState = { counter: 0 };

const useGlobal = useGlobalHook(React, initialState);

export default useGlobal;

Separate actions from components

If you’ve ever used a complex state management library, you know that manipulating global state directly from components is not the best idea.

The best way is to separate the business logic by creating actions that manipulate the state. For this reason, I hope that the last version of our solution will not be accessible to componentssetState()Function, but give a set of actions.

In order to solve this problem, ouruseGlobalHook(React, initialState, actions)Function will receive aactionObject as the third parameter. I would like to add something about this.

  • Actions will be accessiblestoreObject. Therefore, actions can be made throughstore.stateRead status bystore.setState()Write state, even usestate.actionsCall other actions.
  • For organizations, action objects can contain children of other actions. So you may have oneactions.addToCounter(a amount)Or a sub object containing all counter actionsactions.counter.add(a amount)Call.

Final version

The following files are NPM packagesuse-global-hookThe actual file in.

function setState(newState) {
  this.state = { ...this.state, ...newState };
  this.listeners.forEach((listener) => {
    listener(this.state);
  });
}

function useCustom(React) {
  const newListener = React.useState()[1];
  React.useEffect(() => {
    this.listeners.push(newListener);
    return () => {
      this.listeners = this.listeners.filter(listener => listener !== newListener);
    };
  }, []);
  return [this.state, this.actions];
}

function associateActions(store, actions) {
  const associatedActions = {};
  Object.keys(actions).forEach((key) => {
    if (typeof actions[key] === 'function') {
      associatedActions[key] = actions[key].bind(null, store);
    }
    if (typeof actions[key] === 'object') {
      associatedActions[key] = associateActions(store, actions[key]);
    }
  });
  return associatedActions;
}

const useGlobalHook = (React, initialState, actions) => {
  const store = { state: initialState, listeners: [] };
  store.setState = setState.bind(store);
  store.actions = associateActions(store, actions);
  return useCustom.bind(store, React);
};

export default useGlobalHook;