Introduction to React Redux

March 09, 2020

This article is a written version of Rithm School’s React Redux lecture.

The goals of this lecture are to:

  1. Combine a Redux store with a React app
  2. Pass state from the store to a component with useSelector
  3. Dispatch actions to the store with useDispatch

React-Redux

As we incorporate Redux into our React applications, we'll be using a library called react-redux.

By wrapping our application in a top-level component called <Provider>, react-redux will allow us to connect a store for the rest of our application to use.

But, in order for this to work, we'll need a reducer.

Counting Reducer

Here is the reducer from the previous lecture:

const INITIAL_STATE = { count: 0 };

function rootReducer(state = INITIAL_STATE, action) {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };

    case "DECREMENT":
      return { ...state, count: state.count - 1 };

    default:
      return state;
  }
}

export default rootReducer;

When an action is sent to our reducer, depending on what type it is, the state returned will either have a count of plus or minus 1.

The react-redux library gives us a component named <Provider>.

This component accepts a Redux store as one of the props.

To give the rest of our application access to this store, we need to wrap our App component in the react-redux-provided Provider component:

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

import rootReducer from "./rootReducer";
import { createStore } from "redux";
import { Provider } from "react-redux";

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

Connecting Components

Now that our application has been provided access to a store, we can begin interacting with the store directly from components within App.

Accessing the Store

To access values in the store, we'll use the useSelector hook.

The useSelector hook accepts a callback. This callback function uses the store as its first argument and returns whatever data we want to retrieve from the store:

useSelector(store => store.count);

Let's see the useSelector hook in action:

import React from "react";
import { useSelector } from "react-redux";

function FirstCounter() {
  // Accessing count from the store
  const count = useSelector(store => store.count);

  return (
    <div>
      <h2>The count is: {count}.</h2>
    </div>
  );
}

export default FirstCounter;

We can see on Line 6 that we are returning the property count from the store, then displaying it within our component's h2 tag.

Dispatching to the Store

In the previous section, we saw how to access properties from the store.

What if we'd like to make changes to the store?

To make changes, we'll use a different hook, useDispatch, which will allow us to dispatch actions directly to the store we're connected to.

Take a look at an example below:

import React from "react";
import { useSelector, useDispatch } from "react-redux";

function SecondCounter() {
  const count = useSelector(st => st.count);
  const dispatch = useDispatch();
  const up = () => dispatch({ type: "INCREMENT" });
  const down = () => dispatch({ type: "DECREMENT" });
  
  return (
    <div>
      <h2>The count is: {count}.</h2>
      <button onClick={up}> + </button>
      <button onClick={down}> - </button>
    </div>
  );
}

export default SecondCounter;

On Line 6, you can see that we're first setting the return value of our useDispatch hook.

We are then declaring two functions (up and down), each of which will invoke the dispatch function with a particular action as the argument.

💡The value returned from useDispatch is a reference to the dispatch function from the Redux store.

Data Flow

Let's review the data flow when using Redux in a React application.

  1. Store is created. On creation of the store, an initial action is dispatched.
  2. Reducer returns the initial state. In the example code we've been working with, we've set this state to correspond to INITIAL_STATE at the top of our rootReducer.js file.
  3. useSelector hook runs for all connected components. Each component will receive the data they've expressed interest in from the store. The update of these state values will trigger a render of the component.
  4. dispatch that results in new data will cause re-render. From that point onward, if any component receives new data from the store after dispatching, a re-render of the component will occur.

When to Connect

Not every component will need the useSelector and/or useDispatch hooks.

Some components will only need to read data, while others might only need to write.

If you're mapping over an array to render components, you still will continue to pass props directly from parent to child components.

As you continue getting practice with component design, think carefully about when to use the store versus just passing props.

Redux Dev Tools

There is a particular set of Developer Tools specifically for use with Redux, aptly named Redux Dev Tools.

As you move forward with your React-Redux application development, we encourage you to setup the extension for the browser of your choice: Redux Dev Tools

Including Redux Dev Tools

In your index.js file, include the following:

const store = createStore(rootReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__
  && window.__REDUX_DEVTOOLS_EXTENSION__()
);

With that snippet in place, you'll now have access to the application's Redux state directly in your browser!