Translate

Monday, 21 September 2020

How to Replace Redux with React Hooks and the Context API

The most popular way to handle shared application state in React is using a framework such as Redux. Quite recently, the React team introduced several new features which include React Hooks and the Context API. These two features effectively eliminated a lot of challenges that developers of large React projects have been facing. One of the biggest problems was “prop drilling”, which was common with nested components. The solution was to use a state management library like Redux. This, unfortunately, came with the expense of writing boilerplate code. But now it’s possible to replace Redux with React Hooks and the Context API.

In this tutorial, you’re going to learn a new way of handling state in your React projects, without writing excessive code or installing a bunch of libraries — as is the case with Redux. React hooks allow you to use local state inside function components, while the Context API allows you to share state with other components.

Prerequisites

In order to follow along with this tutorial, you’ll need to be familiar with the following topics:

The technique you’ll learn here is based on patterns that were introduced in Redux. This means you need to have a firm understanding of reducers and actions before proceeding. I’m currently using Visual Studio Code, which seems to be the most popular code editor right now (especially for JavaScript developers). If you’re on Windows, I would recommend you install Git Bash. Use the Git Bash terminal to perform all commands provided in this tutorial. Cmder is also a good terminal capable of executing most Linux commands on Windows.

You can access the complete project used in this tutorial at this GitHub Repository.

About the New State Management Technique

There are two types of state we need to deal with in React projects:

  • local state
  • global state

Local states can only be used within the components where they were defined. Global states can be shared across multiple components. Previously, defining a global state required the installation of a state management framework such as Redux or MobX. With React v16.3.0, the Context API was released, which allows developers to implement global state without installing additional libraries.

As of React v16.8, Hooks have allowed implementation of a number of React features in a component without writing a class. Hooks brought vast benefits to the way React developers write code. This includes code reuse and easier ways of sharing state between components. For this tutorial, we’ll be concerned with the following React hooks:

useState is recommended for handling simple values like numbers or strings. However, when it comes to handling complex data structures, you’ll need the useReducer hook. For useState, you only need to have a single setValue() function for overwriting existing state values.

For useReducer, you’ll be handling a state object that contains multiple values with different data types in a tree-like structure. You’ll need to declare functions that can change one or more of these state values. For data types such as arrays, you’ll need to declare multiple immutable functions for handling add, update and delete actions. You’ll see an example of this in a later section of this tutorial.

Once you declare your state using either useState or useReducer, you’ll need to lift it up to become global state using React Context. This is done by creating a Context Object using the createContext function provided by the React library. A context object allows state to be shared among components without using props.

You’ll also need to declare a Context Provider for your context object. This allows a page or a container component to subscribe to your context object for changes. Any child component of the container will be able to access the context object using the useContext function.

Now let’s see the code in action.

Setting Up the Project

We’ll use create-react-app to jump-start our project quickly:

$ npx create-react-app react-hooks-context-app

Next, let’s install Semantic UI React, a React-based CSS framework. This isn’t a requirement; I just like creating nice user interfaces without writing custom CSS:

yarn add semantic-ui-react fomantic-ui-css

Open src/index.js and insert the following import:

import 'fomantic-ui-css/semantic.min.css';

That’s all we need to do for our project to start using Semantic UI. In the next section, we’ll look at how we can declare a state using the useState hook and uplifting it to global state.

Counter Example: useState

For this example, we’ll build a simple counter demo consisting of a two-button component and a display component. We’ll introduce a count state that will be shared globally among the two components. The components will be a child of CounterView, which will act as the container. The button component will have buttons that will either increment or decrement the value of the count state.

Let’s start by defining our count state in a context file called context/counter-context.js. Create this inside the src folder and insert the following code:

import React, { useState, createContext } from "react";

// Create Context Object
export const CounterContext = createContext();

// Create a provider for components to consume and subscribe to changes
export const CounterContextProvider = props => {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={[count, setCount]}>
      {props.children}
    </CounterContext.Provider>
  );
};

We’ve defined a state called count and set the default value to 0. All components that consume the CounterContext.Provider will have access to the count state and the setCount function. Let’s define the component for displaying the count state in src/components/counter-display.js:

import React, { useContext } from "react";
import { Statistic } from "semantic-ui-react";
import { CounterContext } from "../context/counter-context";

export default function CounterDisplay() {
  const [count] = useContext(CounterContext);

  return (
    <Statistic>
      <Statistic.Value>{count}</Statistic.Value>
      <Statistic.Label>Counter</Statistic.Label>
    </Statistic>
  );
}

Next, let’s define the component that will contain buttons for increasing and decreasing the state component. Create the file src/components/counter-buttons.js and insert the following code:

import React, { useContext } from "react";
import { Button } from "semantic-ui-react";
import { CounterContext } from "../context/counter-context";

export default function CounterButtons() {
  const [count, setCount] = useContext(CounterContext);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <Button.Group>
        <Button color="green" onClick={increment}>
          Add
        </Button>
        <Button color="red" onClick={decrement}>
          Minus
        </Button>
      </Button.Group>
    </div>
  );
}

As it is, the useContext function won’t work since we haven’t specified the Provider. Let’s do that now by creating a container in src/views/counter-view.js. Insert the following code:

import React from "react";
import { Segment } from "semantic-ui-react";

import { CounterContextProvider } from "../context/counter-context";
import CounterDisplay from "../components/counter-display";
import CounterButtons from "../components/counter-buttons";

export default function CounterView() {
  return (
    <CounterContextProvider>
      <h3>Counter</h3>
      <Segment textAlign="center">
        <CounterDisplay />
        <CounterButtons />
      </Segment>
    </CounterContextProvider>
  );
}

Finally, let’s replace the existing code in App.js with the following:

import React from "react";
import { Container } from "semantic-ui-react";

import CounterView from "./views/counter-view";

export default function App() {
  return (
    <Container>
      <h1>React Hooks Context Demo</h1>
      <CounterView />
    </Container>
  );
}

You can now fire up the create-react-app server using the yarn start command. The browser should start and render your counter. Click the buttons to ensure that increment and decrement functions are working.

You can also test this code on CodePen.

See the Pen
qBZYyqw
by SitePoint (@SitePoint)
on CodePen.

Let’s go the next section, where we’ll set up an example that’s a bit more advanced using the useReducer hook.

Continue reading How to Replace Redux with React Hooks and the Context API on SitePoint.



No comments:

Post a Comment