Replacing Redux with Context API
July 18, 2020
The Context API is an alternative way to handle data in components without having to incorporate Redux into your app.
Using React.createContext()
, you're essentially creating a globally available JavaScript object.
Create the Context File
In ./src/
create a context
folder with the file colors-context.js
. The directory and file name can be whatever you want.
This is where the states will be managed and updated.
First import React
and useState
from react
.
import React, { useState } from 'react';
The state data colors
will be created with createContext
which is available from the React
package. This ColorsContext
will be used directly in components so it needs to be exported.
export const ColorsContext = React.createContext({
colors: []
});
In place of a reducer, I'm gong to create a ColorsContextProvider
that will update the state. useState
is here to have a local state colorsList
mapped to the ColorsContext.colors
. colorsList
is what will be modified when needed (with using setColorsList
), this in turn updates the ColorsContext.colors
without having to change it directly.
const ColorsContextProvider = props => {
const [colorsList, setColorsList] = useState([]);
return (
<ColorsContext.Provider value={{ colors: colorsList }}>
{props.children}
</ColorsContext.Provider>
)
}
export default ColorsContextProvider;
The ColorsContext.Provider
tags make sure that components using the values in its attribute value
are notified and re-rendered when they are changed.
props.children
makes sure anything within these tags is passed along in the app.
Wrap App in ColorsContextProvider
Now we need a way for the colors
data to be available in the components. If you recall, Redux had Provider
tags that inserted the store into the App. Since the colors
state in already passed in the ColorsContext.Provider
tags, this part doesn't need to import the state itself.
In ./src/index.js
, the ColorsContextProvider
needs to be imported and then wrapped around everything.
import ColorsContextProvider from './context/colors-context';
ReactDOM.render(
<ColorsContextProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</ColorsContextProvider>,
document.getElementById('root')
);
Display State in App.js
Let's try just displaying the colors list in the ./src/App.js
.
Import useContext
from react
and the ColorsContext
.
import React, { useContext } from 'react';
import { ColorsContext } from './context/colors-context';
Create a local context.
const colorsContext = useContext(ColorsContext);
Now colors
can be accessed with colorsContext.colors
in the component.
<ul>
{colorsContext.colors.map(color => {
return <li key={color}>{color}</li>;
})}
</ul>
Context Methods
Now if we want to add or remove a color, the methods need to be set up in the colors-context.js
file.
First add the methods that will be accessed in components to update the colors
state.
export const ColorsContext = React.createContext({
colors: [],
addColor: () => { },
removeColor: () => { }
});
The ColorsContextProvider
will contain the logic for these methods which is similar to how the reducer added and removed a color. Notice that we are using setColorsList
which is actually updating colorsList
not colors
itself. But the App.js
component will still get the update.
const addColorHandler = (color) => {
const newColors = [...colorsList];
newColors.push(color);
setColorsList(newColors);
}
const removeColorHandler = (color) => {
setColorsList(colorsList.filter(aColor => aColor !== color));
}
In the ColorsContext.Provider
tags, map these methods to the ones in ColorsContext
so any component can use them.
return (
<ColorsContext.Provider value={{ colors: colorsList, addColor: addColorHandler, removeColor: removeColorHandler }}>
{props.children}
</ColorsContext.Provider>
)
Add/Remove Colors in Button.js
Similar to App.js
, import useContext
and ColorsContext
in Button.js
and create a local colorsContext
.
Now the methods can be accessed with colorsContext.addColor()
and colorsContext.removeColor()
.
import React, { useState, useContext } from 'react';
import './Button.css';
import { ColorsContext } from '../context/colors-context';
const Button = props => {
const [isAdded, setIsAdded] = useState(false);
const colorsContext = useContext(ColorsContext);
const addColorHandler = () => {
setIsAdded(true);
colorsContext.addColor(props.color);
}
const removeColorHandler = () => {
setIsAdded(false);
colorsContext.removeColor(props.color);
}
return (
isAdded ?
<button onClick={removeColorHandler}>Remove <br /> {props.color}</button> :
<button onClick={addColorHandler}>Add <br /> {props.color}</button>
);
}
export default Button;
Use in Class Components
If using a class component, use contextType
to access the context file data and call this.context
to get the values.
I created a class component that just prints a message stating the current number of colors added to the list.
import React, { Component } from 'react';
import { ColorsContext } from '../context/colors-context';
class ColorsLength extends Component {
static contextType = ColorsContext;
render() {
return (
<p>There are currently {this.context.colors.length} colors added.</p>
)
}
}
export default ColorsLength;
Combine Contexts
Since Redux can combine multiple reducers, I was curious if the same can be done for multiple contexts. Turns out, you can do this by just wrapping each context's tags around the JSX or the whole app like this repo's example.
Note
Instead of wrapping the whole App in context tags, you could use the
Provider
Opens in a new window and
Consumer
Opens in a new window
components to only have specific components subscribe to changes in the values.
With this approach, the logic to add/remove colors would be located in the component using these methods. Personally, I prefer all the logic updating the context should be declared in the context file itself. But this is another approach you could take if it suits your needs.
Conclusion
I found the Context API very easy to implement compared to Redux's use of mapStateToProps
and mapDispatchToProps
. Plus, no need to keep track of action types.
This example can be found in the
context-api-intro repo
Opens in a new window .
Resources: