React Context vs Redux (part1)
Sometimes in our react apps we need some global variables, like user authentications, themes, shopping carts, etc. or maybe you need a state in a big tree of like a dozen of components. without a proper state management we’ll have to pass a state through lot’s of components in our component tree to reach the component we want.
In this tutorial I will compare and review two major state management solutions in react, React context and Redux.
I will create a simple app with both context api and redux, the app contains a product list and a shopping cart. you can see the Final App HERE.
this is a two part article in the first part I’ll create the app with context api, in part 2 I’ll create the same app with redux and compare these two approaches.
Using React Context api:
(complete code in this github repo)
We’re going to see how to create a global state management with context api and hooks. In this project we’ll only use functional components and hooks.
1.create context
First we need to create a context for our cart and import it in every component that we need. I’ll name it listContext:
//list_context.js
import React from 'react';
const listContext = React.createContext();
export default listContext;
2-create store
The app has a global store, which will provide all state and actions needed.
The magic of hooks
hooks were introduced in recent versions of react and finally became stable and ready to use for production in react 16.8. in this article we’ll see how to use hooks with context to make our code more readable and more efficient.
store.js:
import React, { useState, useEffect } from 'react';
import listContext from './list_context';
//global store for our app we can create multiple contexts if needed
function Store({children}){
// the app's initial state
const initialState = {
cart:[],
cartCount:0,
addNew: addNew,
removePd: removePd
}
//initiate app state with initialstates
const [ appstate, setState ] = useState(initialState);
/* pass the state as context's value. all components
wrapped with <Store/> tag will access context's state */
return(
<listContext.Provider value={appstate}>
{children}
</listContext.Provider>
)
3- add necessary functions to store
then we add functions to remove from and add products to cart and get products count to update cartCount :
////// add new product to cart and update cart count
function addNew(pd){
let newList = appstate.cart;
const newItem = {
count:1,
id:pd.id,
name:pd.name
}
const filtered = newList.filter(i =>{
return i.id === pd.id;
});
/* if the product is already in the cart,
update it's count otherwise add it to cart with 1 count */
if(filtered.length > 0){
const pos = newList.map(i => { return i.id; }).indexOf(pd.id);
newList[pos].count += 1;
}else{
newList.push(newItem);
}
setState({...appstate, cart:newList, cartCount:getCartCount()});
}
////// remove product from cart and update cart count
function removePd(indx){
const cartList = appstate.cart;
cartList.splice(indx,1);
setState({...appstate, cart:cartList, cartCount:getCartCount()});
}
////// function to set the number of products in cart
function getCartCount(){
let cnt = 0;
if(appstate.cart.length > 0){
appstate.cart.forEach(item => {
cnt += item.count;
});
}
return cnt;
}
now that the store is ready we have to wrap the components that use our global state with <store> in our case since the app isn’t big and complex and we have only one store we can wrap the whole <App/> with store tags.
index.js:
const render =
<Store>
<Router>
<App/>
</Router>
</Store>
ReactDOM.render(render, document.getElementById('root'));
4-app component and routing
We use the App component to set up our routing including a navbar and two routs that will either load Cart or List components:
App.js:
import listContext from './list_context';
import List from './components/List'
import Cart from './components/Cart'
function App() {
// get cart count from context
const { cartCount } = useContext(listContext);
return (
<div>
<div className='nav'>
<Link to='/'>
<button>products</button>
</Link>
<Link to='/cart'>
<button>{'cart('+cartCount+')'}</button>
</Link>
</div>
<Switch>
<Route exact path='/' component={List}/>
<Route path='/cart' component={Cart}/>
</Switch>
</div>
);
}
export default App;
5- list products and add to cart
This component will list the products in a list with a button to add them to cart:
components/List.js
import React, { useState, useEffect, useContext } from 'react';
import listContext from '../list_context';
function List(){
const [list, setList] = useState([]);
/* we imagine we load the products from a rest api
so we use the useEffect() hook to setList as soon as
the component is loaded */
useEffect(()=>{
// we assume this array was loaded from a rest api:
const productList = [
{name:'book', id:1}, {name:'laptop', id:2}, {name:'game console', id:3}, {name:'radio', id:4}, {name:'notenook', id:56}
]
setList(productList);
},[])
const pdlist = list.map((i,index) => {
return <li key={index}>id:{i.id} | product name: <strong>{i.name}</strong> <Addbutton pd={i}/></li>
})
return (
<ul>
{pdlist}
</ul>
);
}
//// a button to add new product to cart
function Addbutton(props){
const stt = useContext(listContext);
return(
<button onClick={()=>stt.addNew(props.pd)}>
add to cart
</button>
)
}
export default List;
6-shopping cart
using the same useContext hook we can both read and update our global cart state in our cart component:
import React, { useContext } from 'react';
import listContext from '../list_context';
function Cart(){
// get shopping cart array from listcontext
const { cart } = useContext(listContext);
const cartlist = cart.map((i,index) => {
return (
<tr key={index}>
<td>{i.id}</td>
<td>{i.name}</td>
<td>{'x'+i.count}</td>
<td>{<Removebutton pd={i}/>}</td>
</tr>
)
})
if(cart.length > 0){
return (
<div style={
{padding:'15px'}
}>
<table className='c'>
<tr className='thead'>
<th>ID</th>
<th>NAME</th>
<th>QUANTITY</th>
<th>ACTIONS</th>
</tr>
{cartlist}
</table>
</div>
)
} else{
return <p className='c'>cart is empty</p>
}
}
Removebutton will remove product from cart by index of product:
/// a button to remove products from cart by index
function Removebutton(props){
const state = useContext(listContext);
return(
<button onClick={()=>state.removePd(state.cart.indexOf(props.pd))}>
remove
</button>
)
}
The app now functions as expected, we can list products in List component, add them to cart and list them in Cart.js and remove them from cart.
In part 2 I’ll create the same app using redux and compare these two state management methods.