Categories
Redux

Using the React-Redux Hooks API to Manipulate Store State

Spread the love

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to use the useDispatch and useStore hooks in the React-Redux hooks API, and also look at performance and stale state issues.

useDispatch

We can use the useDispatch hook to get the dispatch function to dispatch actions to the store.

For example, we use useDispatch as follows:

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

function count(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(count);

function App() {
  const count = useSelector(state => state);
  const dispatch = useDispatch();
  return (
    <div className="App">
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
      <p>{count}</p>
    </div>
  );
}

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

In the code above, we called useDispatch in App to get the dispatch function so that we can dispatch actions from the store.

Then we create functions that call dispatch to pass into the onClick prop.

useStore

We can use the useStore hook to get the reference to the Redux store. It’s the same one that we passed into the Provider component.

This shouldn’t be used frequently since we aren’t supposed to access the store directly within a React app since it doesn’t update according to the React update cycle.

Stale Props and “Zombie Children”

Stale props mean that a selector function relies on a component’s props to extract data, a parent component would re-render and pass down new props as a result of an action, but the component’s selector functions execute before the component has a chance to re-render with those new props.

This causes outdated data to be displayed. in the child component or an error being thrown.

“Zombie child” refers to the cases where multiple nested connected components are mounted ina first pass, causing a child component to subscribe to the store before its parent. Then an action is dispatched that deletes data from the store. The parent component would stop rendering the child as a result.

Because the child subscribed to the store first, its subscription runs before the parent stops render. When it reads a value based on props, the data no longer exists. Then an error may be thrown when the extraction logic doesn’t check for the case of the missing data.

Therefore, we should rely on props in our selector function for extracting data.

connect adds the necessary subscription to the context provider and delays evaluating child subscriptions until the connected component has re-rendered. Putting a connected component in the component tree just above the component using useSelector will prevent the issues above.

Photo by Ezra Jeffrey-Comeau on Unsplash

Performance

useSelector will do a reference equality comparison of the selected value when running the selector function after an action is dispatched.

It’ll only re-render if the previous state is different from the current state. connect and useSelector doesn’t prevent the component from re-rendering because of its parent re-rendering.

To stop parent re-rendering from re-rendering the child, we can use memoization to prevent that.

For example, we can write the following:

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

function count(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(count);

function App() {
  const count = useSelector(state => state);
  const dispatch = useDispatch();
  return (
    <div className="App">
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
      <p>{count}</p>
    </div>
  );
}

App = React.memo(App);

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

In the code above, we added the React.memo call as follows:

App = React.memo(App);

to cache existing results and preventing re-render if the state remains the same.

Conclusion

We can use the useDispatch hook to get the dispatch function to update the store’s state.

There’s also a useStore hook to get a reference of the Redux store in our React components.

However, we shouldn’t access the store directly with useStore since we’re supposed to use useSelector to get the latest state from the store and use useDispatch to dispatch actions.

Also, we have to be aware of stale props from parent components and child components that have been removed because of state changes throwing errors and displaying incorrect data.

We should also memoize the state of components to cache them so that they don’t have to re-render when nothing changed.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *