Categories
React

How to Share States Between React Components?

Spread the love

Sharing states between components is something we’ve to do a lot with React.

In this article, we’ll look at ways we can use to share states between components in React.

Lift State Up

If 2 child components have the same parent component, then we can life the state we want to share from the child component to the parent.

For instance, we can write:

import React, { useState } from "react";

const DescendantA = ({ count, onCountChange }) => {
  return (
    <button onClick={() => onCountChange((count) => count + 1)}>
      Click me {count}
    </button>
  );
};

const DescendantB = ({ count, onCountChange }) => {
  return (
    <button onClick={() => onCountChange((count) => count + 1)}>
      Click me {count}
    </button>
  );
};

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <DescendantA count={count} onCountChange={setCount} />
      <DescendantB count={count} onCountChange={setCount} />
    </>
  );
}

We have the DescendantA and DescendantB components which both have App as their common ancestor.

So to share a state, we put the state in App .

We have the count state which is defined in App .

And we pass in count to both the DescendantA and DescendantB components.

Also, we pass in onCountChange to DescendantA and DescendantB .

And in DescendantA and DescendantB , we call onCountChange and display the count value.

Context API

Another way to share states between 2 components is to use the Context API.

The Context API lets us share states between any component in the context provider.

For instance, we can write:

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

const CountContext = React.createContext("count");

const DescendantA = () => {
  const { count, setCount } = useContext(CountContext);

  return (
    <button onClick={() => setCount((c) => c + 1)}>Click me {count}</button>
  );
};

const DescendantB = () => {
  const { count, setCount } = useContext(CountContext);

  return (
    <button onClick={() => setCount((c) => c + 1)}>Click me {count}</button>
  );
};

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <CountContext.Provider value={{ count, setCount }}>
      <DescendantA />
      <DescendantB />
    </CountContext.Provider>
  );
}

We call React.createContext to create the context.

Then we define DescendantA and DescendantB which calls useContext to get the context.

The value prop of the CountContext.Provider is what’s returned by useContext .

So we can get the count and setCount getter and setter from the returned value.

And in each component, we have the button to set the count .

In App , we wrap CountContext.Provider around DescendantA and DescendantB so we can use the context in both components.

State Management Solution

Another way to share data between multiple components is to use a state management solution.

A popular state management solution for React apps is Redux and React-Redux.

To use it, we can write:

index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import App from "./App";

const rootReducer = (state = 0, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    default:
      return state;
  }
};

const store = createStore(rootReducer);

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

App.js

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

const DescendantA = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => state);

  return (
    <button onClick={() => dispatch({ type: "INCREMENT" })}>
      Click me {count}
    </button>
  );
};

const DescendantB = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => state);

  return (
    <button onClick={() => dispatch({ type: "INCREMENT" })}>
      Click me {count}
    </button>
  );
};

export default function App() {
  return (
    <>
      <DescendantA />
      <DescendantB />
    </>
  );
}

In index.js , we have the rootReducer with that takes the state value and return the latest value of the store based on the action type we dispatch.

We pass in the rootReducer to createStore to create a Redux store.

Then we pass in the store to the React-Redux Provider component to let us use the store in any component inside the Provider .

In App.js , we have DescendantA and DescendantB which calls the useDispatch hook to get the dispatch function.

dispatch lets us dispatch an action to the store.

Also, we call the useSelector hook with a callback to return the value in the callback, which is the latest return value of rootReducer .

When we click the button, we call dispatch with the type set to 'INCREMENT' to commit the action with type 'INCREMENT' .

Now we should see count updated.

Conclusion

There’re several ways we can use to let us share states between components.

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 *