March 31, 2019

hosein

Articles Tutorials

React Context vs Redux (part2)

In previous article We created a simple shopping cart app with react context api and hooks. in this article we’ll see how different this project can be if we use redux. and then compare these two approaches and see what advantages and disadvantages they have.

full project in this github repo

Redux overview

Redux is a open source state management library that can be used in any javascript app and it’s not only for react.

despite what a lot of people think, learning redux and using it isn’t hard. you just have to learn it’s fundamentals and concepts before starting to code.

Redux follows a predictable and functional programming concept. using redux reducers you can manage states and actions in different parts of your applications.

but is it better than context api? we’ll find out later in the article.

Redux with react

In order to use redux with react more easily. we have to install react-redux , an extension to redux that includes some functions specific to react.

Install redux

using npm  install redux and react-redux:

npm install --save redux
npm install --save react-redux

Create Reducer

A reducer is a function to manage the app’s state and actions to update the state, In the previous article I tried to create a similar function with context and hooks. store.js

The difference is instead of regular functions in our state, we have actions with action types. by action types we can know which property of the state should be updated.

store/reducer.js:

/* we first have to initiate our store,
 in this case we only have a empty cart: */

const initialState = {
    cart:[]
}

const reducer = (state = initialState, action) =>{

    /* YOU CAN'T MUTATE THE STATE DIRECTLY,
    just like react class component states we have
    to update the state in an immutable way */

    const newState = {...state}
    
    /* we will either add products to cart or
    remove them from it so we only have two action
    types */

    /* 'type' isn't the only property of action,
    we can have other properties like 'pd' and 'index' */

    if(action.type === 'add_cart'){
        addNew(action.pd);
    }

    if(action.type === 'remove_cart'){
        removePd(action.index);
    }
.
.
.

then we add functions ‘addnew’ and ‘removepd’ to add product to cart or remove from it:

function addNew(pd){
        
        const newItem = {
          count:1,
          id:pd.id,
          name:pd.name
        }

        const newCart = [...newState.cart];
    
        const filtered = newCart.filter(i =>{
          return i.id === pd.id;
        });
    
        if(filtered.length > 0){
          const pos = newCart.map(i => { return i.id; }).indexOf(pd.id);
          newCart[pos].count += 1;
          newState.cart = newCart;
        }else{
            newCart.push(newItem);
            newState.cart = newCart;
        }
        
      }
    
      ///////////////////
    
      function removePd(indx){
        const newCart = [...newState.cart]
        newCart.splice(indx,1);
        newState.cart = newCart;
      }

and at the end of the reducer return the newState to update the state:

.
.
.
return newState;

}

export default reducer;

just like context, in redux we have to wrap the components we want to access the state in with  provider tags:

index.js:

import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './store/reducer'

/* create a store with reducer*/
const store = createStore(reducer);

const routs = 
 <Provider store={store}>
    <Router>
    <App/>
    </Router>
 </Provider>


ReactDOM.render(routs, document.getElementById('root'));

App component is the same as previous part, we just do a basic routing.

accessing and updating state from redux store

while we could easily access all of our state’s properties with react’s useContext() hook. in redux we have to do this in a different way.

by using mapStateToProps() and mapdispatchToProps() functions we have to inject state and actions to components as props, and we have to do this for every component that needs to access our global state.

List.js:
class List extends Component {

    constructor(props) {
      super(props);
      this.state = {
        list: [
          {name:'book', id:1}, {name:'laptop', id:2}, {name:'game console', id:3}, {name:'radio', id:4}, {name:'pen', id:5}
      ]
       }
    }
    
    // show products in a list
    render() { 
      let lst = this.state.list;
      const pdlist = lst.map((i,index) => {
        return <li key={index}>
id:{i.id} | product name: <strong>{i.name}</strong> 

        <button style={{marginLeft: '15px'}} onClick={() => this.props.newPd(i)}>
        add to cart
      </button> 
      
      </li>
      })
  
      return ( 
            <ul>
              {pdlist}
            </ul>
        );
    }
  }

  ////////////

  // read states from store
  const mapStateToProps = (state) => {
    return {
        cart:state.cart,
    }
  }

//read actions from store
const mapDispatchToProps = (dispatch) =>{
    return {
        newPd: (pd) => dispatch({type:'add_cart', pd:pd}),
    }
}

  // we have to use redux's connect method to map state and actions
// to the component
  export default connect(mapStateToProps, mapDispatchToProps)(List);

We must do the same thing for Cart component:

Cart.js:

class Cart extends Component {

// show cart items in a table
    render() { 
      let crt = this.props.cart;
      const cartlist = crt.map((i,index) => {
        return (
        <tr key={index}>
          <td>{i.id}</td>
          <td>{i.name}</td>
          <td>{'x'+i.count}</td>
          <td>
            <button onClick={()=>this.props.removePd(this.props.cart.indexOf(i))}>
              remove
            </button>
          </td>
        </tr>
        )
      })
  
      if(crt.length > 0){
  
      return ( 
  
            <div style={
              {padding:'15px'}
            }>
            <table className='c'>
              
              <thead>
              <tr className='thead'>
                <th>ID</th>
                <th>NAME</th>
                <th>QUANTITY</th>
                <th>ACTIONS</th>
              </tr>
              </thead>

              <tbody>
              {cartlist}
              </tbody>
            </table>
            </div>
        )
  
          } else{
            return <p className='c'>cart is empty</p>
          }
    }
  }

  // read states from store
const mapStateToProps = (state) => {
    return {
        cart:state.cart,
    }
  }

  //read actions from store
const mapDispatchToProps = (dispatch) =>{
    return {
        removePd: (indx) => dispatch({type:'remove_cart', index:indx}),
    }
}
  
  
  export default connect(mapStateToProps,mapDispatchToProps)(Cart);

so the app now works as expected:

context and redux. which to use and when to use?

So we built the same app using context api and redux. but which one is better and more efficient to use? let’s compare their different aspects.

comparison:

App size: after building both apps in npm, the redux version had 750kb size while  without redux it’s size is 660kb not much difference in this low size app and it shouldn’t be much different in bigger apps because redux is a pretty low size library. Winner: draw

Flexibility:  with redux you have to follow a mostly specific pattern which is predictable and easy to understand and implement. but as I did in the previous part of the article, with combination of context api and hooks you can make any state management system that you want. you can even implement the reducer/action model with react’s built in useReducer() hook. or even create your own hooks. Winner:  React Context

Debugging: React dev tools extension is a good way to inspect and debug react applications. but with redux dev tools  you can have much more control over app’s state and a state timetravel feature. specially in large apps this can be helpful. Winner: Redux

Code readability and ease of use: thanks to react’s useContext() hook state management has never been easier of course in large, real world apps you have to implement context api much more intelligently than I did in this sample app but still obviously with only functional components and  context api we can have a cleaner code.

in contrast you have to map state to props and if needed map dispatch to props with redux’s connect higher order component for Every container component. Winner: React Context

Features: Redux is a very powerful library with a lot of features out of the box if you need middleware and redux saga or store data to local storage using redux persist redux will come in handy. Winner: Redux

Conclusion

if you have a small to medium scale app and you only need to pass and access state in various parts of your app then context and hooks are a easy and fast way to achieve what you want.

however in large scale apps with lots of data and state to manage and if you want to make debugging for you and your team a little easier then maybe you should consider taking advantage of redux.

this example project was just for learning how these two approaches to state management work, at the end of the day you are the one who has to decide how to plan your projects according to the app’s scale and your personal preferences.