Author: Chen Junsheng

React Hooks are available at [email protected]. I’ve recently started using it for internal projects at one or two companies.

Read the document for those who don’t know Hooks. This article will not cover Hooks in detail, but only one way to use them.

Generally, if React is not used in large applications and the front and back end data interaction logic is not complex, simple business scenarios can be met by writing components directly according to normal procedures. As business scenarios get bigger and more numerous, the data communication between components (state management, but I prefer to call it data communication) gets more and more complex. So we introduced Redux to maintain our increasingly complex data communications.

Train of thought

With that in mind, I didn’t introduce Redux at the beginning of my application development, because I thought of it as a small project. As you get deeper into the project, it’s not that simple.

But it’s not too complicated, and now I’m looking at the Context. Context provides a Provider and a Consumer (producer/Consumer mode). At the top level, a Provider is provided, and the children consume the data and methods in the Provider through the Consumer.

Through this concept, we share the same top-level Provider among components at different levels and use consumers internally to consume shared data.

Once we can share data, the remaining question is how do we change the data in the Provider? The answer: useReducer.

All right, so with that in mind, let’s implement it.

The instance

Let’s say we have a Parent element at one level that needs to share state. We call it Parent, and below Parent there are two children between different levels. For the sake of a simple example, assume that there is common logic in both children.

import React from "react"

function Parent() {
  const colors = ['red'.'blue']
  return (
    <>
      <Child1 color={colors[0]} />
      <Child2 color={colors[1]} />
    </>
  )
}

function Child1(props) {
  return (
    <div style={{ background: props.color }}>I am {props.color}</div>
  )
}

function Child2(props) {
  return (
    <div style={{ background: props.color }}>I am {props.color}</div>
  )
}

Copy the code

We have now constructed such a parent structure that we can now share the state of the parent component by passing properties to the child component. But if the hierarchy increases, so does the level at which we pass the attributes. That’s obviously not what we want.

Now let’s introduce the Context.

We first initialize the Context we need using the createContext method.

import React, { createContext } from "react"

const Context = createContext({
  colors: ['red'.'blue']})Copy the code

Then we import the Context in Parent and Child and use useContext to get the shared data:

import React, { useContext, createContext } from "react"

const Context = createContext({
  colors: []})function Parent() {
  const initState = {
    colors: ["red"."blue"]}return (
    <Context.Provider value={{ colors: initState.colors}} >
      <>{/* Pretend these places have different levels */}<Child1 />
        <Child2 />
      </>
    </Context.Provider>
  )
}

function Child1(props) {
  const { colors } = useContext(Context);

  return (
    <div style={{ background: colors[0] }}>
      I am {colors[0]}
    </div>} // omit the Child2 code, same as Child1Copy the code

Now you just get the data and render it, and then you go one step further and change the color by clicking on the element. Here we need to use the useReducer to simulate triggering changes.

First we need a Reducer to handle the triggered changes.


function reducer(state, action) {
  const { colors } = action
  if (action.type === "CHANGE_COLOR") {
    return { colors: colors }
  } else {
    throw new Error()}}Copy the code

Here I’ve simplified the handling of actions, but you can also extend them.

Now we add a method dispatch to the Provider that provides the change.

import React, { useContext, createContext } from "react"

const Context = createContext({
  colors: []})function Parent() {
  const initState = {
    colors: ["red"."blue"]}const [state, dispatch] = useReducer(reducer, initState)

  return (
    <Context.Provider value={{ colors: state.colors.dispatch: dispatch}} >
      <>{/* Pretend these places have different levels */}<Child1 />
        <Child2 />
      </>
    </Context.Provider>)}Copy the code

The child component then triggers the change:


function Child1(props) {
  const { colors, dispatch } = useContext(Context)

  return (
    <div
      style={{ background: colors[0]}}onClick={()= >
        dispatch({
          type: "CHANGE_COLOR",
          colors: ["yellow", "blue"]
        })
      }
    >
      I am {colors[0]}
    </div>)}// omit the Child2 code, same as Child1
Copy the code

At this point, this small state sharing is complete. This was the beginning of the idea of state sharing that we implemented without Redux. See Tiny Redux for complete code and examples.

The advanced

In a real application, our business scenario would be more complex, such as our data is dynamically acquired.

In this case you can pull out the Provider and initialize the Context when the Parent data comes back.


function Provider (props) {
  const { colors } = props
  const initState = {
    colors,
  }
  const [state, dispatch] = useReducer(reducer, initState)

  return (
    <Context.Provider value={{ colors: state.colors.dispatch: dispatch}} >
      {props.children}
    </Context.Provider>)}Copy the code

Then we do asynchronous operations in the Parent and pass dynamic data to the Provider:


import React, { useState, useEffect } from "react"

function Parent (props) {
  const [data, setData] = useState()
  const [url, setUrl] = useState('https://example.com')

  useEffect((a)= > {
    fetch(url).then(res= > setData(data))
  }, [url])

  if(! data)return <div>Loading ...</div>

  return (
    <Provider colors={data}>
      <>{/* Pretend these places have different levels */}<Child1 />
        <Child2 />
      </>
    </Provider>)}Copy the code

in-depth

We can go one step further and make our state management mechanism even more streamlined.

First, define the Context we need at some component level. Let’s say we’re at the top level (global state management).

import React from 'react'

// Create the Context we need
export const AppContext = React.createContext(null)
Copy the code

We then pass the return value of the useReducer directly to the AppContext.provider.

import React, { useReducer } from 'react'

/ / global Provider
export function AppProvider ({reducer, initValue, children}) {
  return (
    <AppContext.Provider value={useReducer(reducer, initValue)} >
      {children}
    </AppContext.Provider>)}Copy the code

Finally, add a custom hooks to get the state and methods in the AppContext. Write Once, Run Anywhere 🙂

import React, { useReducer, useContext } from 'react'

export const useAppState = (a)= > useContext(AppContext)
Copy the code

Finally, our complete state.js code looks like this:

import React, { useContext, useReducer } from 'react'

export const AppContext = React.createContext(null)

export function AppProvider ({reducer, initValue, children}) {
  return (
    <AppContext.Provider value={useReducer(reducer, initValue)} >
      {children}
    </AppContext.Provider>
  )
}

export const useAppState = () => useContext(AppContext)
Copy the code

Components used in:

import { AppProvider, useAppState } from "./state"

function App() {
  const initState = {
    colors: ["red"."blue"]}function reducer(state, action) {
    const { colors } = action;
    if (action.type === "CHANGE_COLOR") {
      return { colors: colors };
    } else {
      throw new Error();
    }
  }

  return (
    <AppProvider initValue={initState} reducer={reducer}>
      <div>{/* Pretend these places have different levels */}<Child1 />
        <Child2 />
      </div>
    </AppProvider>)}function Child1(props) {
  const [state, dispatch] = useAppState()

  return (
    <div
      style={{ background: state.colors[0]}}onClick={()= >
        dispatch({
          type: "CHANGE_COLOR",
          colors: ["yellow", "blue"]
        })
      }
    >
      I am {state.colors[0]}
    </div>)}Copy the code

See Tiny Redux for complete code and examples.

conclusion

Such small state management mechanisms can even be placed in a component rather than in a global environment such as Redux. This allows us to write apps that are more flexible, rather than just throwing states into the Store. Of course you can also write an AppProvider to manage global state. React Hooks + Context provides this convenience.

Hooks delicious!

Small programs can also be developed using hooks. Learn more about Refactoring Your Small Programs using React Hooks.