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.