Redux/Context API for state management in React.

1/9/2021

7 min read

31

During the process of developing React applications, I've had times when I had to decide if I should use Redux or the fairly new hook (available from React's version 16.3) called ContextAPI for managing state in my application. For the most part, I have used Redux, but I feel Context API is a little less daunting to me. Anyway, let me explain you through to manifest if either one's actually better.

We will succinctly look through what these technologies do and how they do, which will probably help you decide which one should you choose for your next project.

Redux - Library for State Management

Redux is a JavaScript library used to manage the state of applications (mainly popular with React). By "state" I mean everything that is rendered on the view. From adding an element to the DOM or removing an element from the DOM, everything is taken care of by Redux.

redux-flow

redux-flow

Listed below are the components that makeup Redux:

  • State: A state is where all the logic of your application lies. Basically, the state describes how your application works, and the UI is rendered based on the state.
  • Actions: Actions are methods that are dispatched to trigger an event. Simply, an action is a way to notify that an event has occurred within the application.
  • Reducers: Reducers are methods that catch different actions triggered and modifies the logic to change and update the state of the application.
  • Subscriptions: Subscriptions are methods to make use of the state in your React application (by the components).

Now since we know a little bit about Redux let's see-through how all of these components work together.

Action Creators a.k.a. Actions

In Redux, actions are methods that are triggered to indicate that an event has occurred within the application. An application can have a few different actions depending upon the type of event that occurs within an application.

JavaScript
export const ADD_PRODUCT_TO_CART = 'ADD_PRODUCT_TO_CART'
export const REMOVE_PRODUCT_FROM_CART = 'REMOVE_PRODCUT_FROM_CART'
 
export const addProductToCart = product => {
      ...
      dispatch({
        type: ADD_PRODUCT_TO_CART,
        payload: product,
      })
  ...
  }
}
 
export const removeProductFromCart = productId => {
      ...
      dispatch({
        type: REMOVE_PRODUCT_FROM_CART,
        payload: productId,
      })
  ...
  }
}

The actions pretty much notify about the event that has occurred within the application. The actions method sends in the type of action triggered within its "type" field. All the additional information on what has changed is put into the "payload" field for the reducer to decide any state change.

Reducer Functions

Here we have imported the actions to essentially let the Reducer function know about the actions that are valid and available.

JavaScript
import { ADD_PRODUCT_TO_CART, REMOVE_PRODUCT_FROM_CART } from "./actions";
 
const initialState = {
  products: [
    { id: "1", title: "...", price: 10.0 },
    { id: "2", title: "...", price: 20.0 },
    // ...
  ],
  cart: [],
};
 
const Reducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_PRODUCT_TO_CART:
      return { ...state, cart: updatedCart };
    case REMOVE_PRODUCT_FROM_CART:
      return { ...state, cart: updatedCart };
    default:
      return state;
  }
};

The Reducer function receives "initialState" and "actions" as arguments. Based on the type of action handled in the switch statement, the corresponding state change takes place. This is the function that takes care of mutating the state according to the type of event that has occurred.

Passing in store as a prop

We then pass in the store as a prop demonstrated in the code section below to make the state accessible to the React components. A store is passed in as a wrapper around the root application component.

JavaScript
import Reducer from "./store/reducers";
 
const store = createStore(Reducer, applyMiddleware(reduxThunk));
 
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

We then use the connect method to connect the "mapStateToProps" and "mapDispatchToProps" function to the "ProductsPage" component.

  • mapStateToProps: It is used to provide the store data to the components.
  • mapDispatchToProps: It is used to provide the actions as props to your component.
JavaScript
class ProductsPage extends Component {
  render() {
    return <React.Fragment>...</React.Fragment>;
  }
}
 
const mapStateToProps = (state) => {};
 
const mapDispatchToProps = (dispatch) => {};
 
export default connect(mapStateToProps, mapDispatchToProps)(ProductsPage);

Context API - Global state management for React applications

Before the advent of Context API, the data was passed through each component as a prop. So in order to pass down a prop to a child component it had to travel all the way down from the parent component to the child component (Top-down approach) in the state tree. This scenario is not preferable since props are passed down to components that don't need them. Hence Context API now provides a way to expose a shared entity called "state" that can be used by the components without having to explicitly pass a prop through every level of the tree.

💡

Prop Drilling is the process by which you pass data from one part of the React Component tree to another by going through other parts that do not need the data but only help in passing it around.

context-api

context-api

Listed below are the components that mainly makeup Context:

  • Context Object
  • Context Provider
  • Context Consumer

Context Object

We have created a context object as demonstrated below.

JavaScript
const ShopContext = React.createContext(defaultValue);

The context object allows the React components to subscribe to this context object allowing them to read the current context value.

Providing Context

Upon providing the context, it is now available to all the components to interact with it. Context is usually provided in the root component that wraps all the child components. This allows all the child components to receive their piece of the context when required.

Note: The context can be made available to all the components of the application or to only a few components down the tree. For the former case, we can pass the context provider in the root component. And for the latter, we can provide it to any particular component down the component tree.

The context provider is implemented as below:

JavaScript
class App extends Component {
  render() {
    return (
      <ShopContext.Provider value={{
          products: [],
          cart: []
        }
      }>
      ...
      </ShopContext.Provider />
    );
  }
}

The value prop on <ShopContext.Provider> is the entity forwarded to the child components. Any change in this value can trigger a change in the application as well.

Consuming from Context

<ShopContext.Consumer> is a wrapper component that is used to inject the value of the Context. It is subscribed to any context changes.

JavaScript
class ProductsPage extends Component {
  render() {
    return (
      <ShopContext.Consumer>
        {context => (
          <React.Fragment>
            <MainNavigation
              ...
            />
        )}
      </ShopContext.Consumer>
    )
  }
}
 
export default ProductsPage

The <Context.Consumer> actually requires a function as a child. The function receives the current context value and returns a React node. The context is the exact same object passed to value in our <ShopContext.Provider>.

Which is better? Redux or Context?

Being very honest, there's no definitive answer to this question yet. As we have clearly seen that Redux, in order to work coherently within the React ecosystem requires a few extra libraries to set up. This takes a toll on any application as it increases the bundle size of the application to be shipped. While Context being just another React hook requires very less configuration to beat the similar purpose of prop drilling.

To be more decisive I would say I would choose Context for applications that require less frequent updates on the other hand with a more complex state having immediate or frequent updates, I would choose Redux by a margin. This is just to avoid the unnecessary re-render on each update on Context. Since Context happens to re-render the components in every state update, it is only viable to make use of Redux that only re-renders the updated components.

There is no conclusive evidence on why one is better than the other and will forever be just a matter of opinion.

References