State management is a really important part of any React application. It helps keep data in sync across components and allows for communication between them. Two of the most popular state management libraries are React Context and Redux. As a developer, it can be tough to choose between them, but don't worry - in this blog post, we'll compare React Context and Redux to help you decide which one to use for state management.
React Context is an awesome library that makes it easy to pass data down through the component tree without the need for prop drilling. It provides a way to share data between components that aren't directly connected. React Context is actually built into React, so you don't need any additional dependencies.
Here's an example:
// CounterContext.js
import React, { useState, createContext } from 'react';
const CounterContext = createContext();
const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
const increment = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<CounterContext.Provider value={{ count, increment }}>
{children}
</CounterContext.Provider>
);
};
export { CounterProvider, CounterContext };
Here, we’re creating our context, or the “data” we want to interact with in our application. Typically, you’d create a new context file for each “section” of related data in your app such as “auth”, “clients”, “pizzas”…you get the point...
Much like in a React component, we can create local state in our context, and functions that interact with the state. We then pass them through to the context provider in order for us to be able to interact with the state throughout our entire application later on.
We much remember to export the CounterProvider, and the CounterContext.
// index.js
import React from 'react';
import CounterProvider from './CounterProvider';
import Counter from './Counter';
const App = () => {
return (
<CounterProvider>
<Counter />
</CounterProvider>
);
};
export default App;
At the root our application is typically where you’d want to then wrap your component tree in the context provider, then our data can trickle down through our components. Here, I’ve chosen to do it in my highest level index.js file.
// MyComponent.js
import React, { useContext } from 'react';
import CounterContext from './CounterContext';
const Counter = () => {
const { count, increment } = useContext(CounterContext);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
In order to the hook into the context in any component in our application, we can import the useContext
hook from React, import our Context from the context file we just created, and then use whatever state or functions we like from it in our components. Easy!
One of the main benefits of React Context is that it's easy to set up and use. It eliminates the need for a complex architecture that's required in Redux. With React Context, you can define a provider component that wraps the components that need access to the data. The provider component can receive the data as a prop, which can then be accessed by the child components using the useContext hook.
Redux is another popular state management library that's been widely adopted by the React community. It provides a centralised store that holds the state of the application. Redux is based on the Flux architecture, which separates the application state from the UI components.
Redux has a more complex setup compared to React Context and requires a different way of thinking about the data in our application, but it does typically allow for larger scale state management. It requires you to define actions, reducers, and a store. The actions are used to describe what happened in the application, the reducers modify the application state based on the actions, and the store holds the state of the application.
Here's an example:
// store.js
import { createStore } from 'redux';
const INCREMENT = "INCREMENT";
function increment() {
return {
type: INCREMENT
};
}
function counter(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
default:
return state;
}
}
const store = createStore(counter);
export default store;
So what’s going on here? import { createStore } from 'redux';
We’re importing the createStore
function from Redux. The createStore
function is super important in Redux as it creates a Redux store, which is like the brain of the application. const INCREMENT = "INCREMENT";
this line declares a constant variable named INCREMENT
and assigns it the string value "INCREMENT"
. This constant is used as an action type in the Redux store. It's like a label that helps the store know what to do when an action is dispatched. We then create our first action for our Redux store “increment”. In this case, the action has a type
property set to the INCREMENT
constant defined earlier. Actions are like messages that tell the store what to do.
function counter(state = 0, action) { ... }
: This is our first Redux reducer function. It takes two parameters: state
and action
. The state
parameter represents the current state of the application (which starts at 0), and the action
parameter represents the action dispatched in the store. Inside the reducer, a switch statement is used to handle different action types. In this case, when the action.type
matches the INCREMENT
constant, the state is incremented by 1. Otherwise, the current state is returned as is. const store = createStore(counter);
: This line creates a Redux store by invoking the createStore
function and passing the counter
reducer as an argument. This store will hold the application state and updates it based on the dispatched actions. Finally, we export the store
object as the default export of the file.
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import MyComponent from './MyComponent';
ReactDOM.render(
<Provider store={store}>
<MyComponent />
</Provider>,
document.getElementById('root')
);
We then need to connect the Redux store to our application. First, we import the necessary dependencies such as React, ReactDOM, and Provider from react-redux
. We also import the Redux store from the store.js
file and the MyComponent
component from the MyComponent.js
file.
After that, we use the ReactDOM.render()
function to render the React application. The application is wrapped with the Provider
component, which takes the Redux store as a prop. This allows all components within the application to access the Redux store and its state.
The main component being rendered is MyComponent
, which is a custom React component that utilises the Redux store. By wrapping the application with Provider
and passing in the store, the MyComponent
component can access the store's state and dispatch actions to update the state.
// MyComponent.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './store';
function MyComponent() {
const count = useSelector(state => state);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
}
export default MyComponent;
Finally, our MyComponent
...component. In order to interact with our state, and change it in our app we need to import useSelector
and useDispatch
, from the react-redux
library. Then, we import the increment
function that we created from the store.js
file.
Next, we use the useSelector
hook to access a specific piece of state from the Redux store. In this case, we retrieve the count
value from the store. We also use the useDispatch
hook to get a function that we can use to dispatch actions to the Redux store. This allows us to update the state in the store.
In the component's render function, we are displaying the current count value and a button to use our increment Redux action. Nice!
One of the main benefits of Redux is its ability to manage complex state and data flow in large applications. It provides a standardised way of storing and accessing data, which makes it easier to maintain and debug the application. However, it requires more setup and has a steeper learning curve compared to React Context.
The choice between React Context and Redux depends on the complexity of your application. If you have a small application with simple data flow, React Context is a good choice. It's easy to set up and use, and it eliminates the need for a complex architecture. However, if you have a large application with complex data flow, Redux is a better choice. It provides a centralised store that makes it easy to manage the state of the application.
React Context and Redux are both popular state management libraries in the React ecosystem. React Context is simple and lightweight, while Redux provides a more complex but standardised way of managing the application state. The choice between the two depends on the complexity of your application. We hope that this blog post has helped you decide which one to use for your next React project. Thanks for reading! 😊