DEV Community

Cover image for Super Context: Unleashing Blazing Performance for Your ReactJS Apps!
Guido Guevara
Guido Guevara

Posted on

Super Context: Unleashing Blazing Performance for Your ReactJS Apps!

React's Context API is useful for sharing data across components, but it can negatively impact performance due to unnecessary re-renders caused by changes in the context state, as any component consuming the context will be re-rendered whether they are directly affected by the state update or not. This blog post introduces "Super Context," a
dependency-free lightweight library that optimizes state management with React's Context API, minimizing re-renders.

Installation

To get started with Super Context, you can install the package using either npm or yarn:

npm install super-context

or

yarn add super-context

Setting up your application

To begin using Super-Context, you need to create a super context by calling the createSuperContext function. This function takes an initial state as an argument and returns a set of hooks and components for managing and accessing the state.

import { createSuperContext } from 'super-context';

// Define the initial state
const initialState = {
  // Your initial state properties here
  count: 0,
  messages: []
};

// Create the super context
const { Provider, useStore } = createSuperContext(initialState);
Enter fullscreen mode Exit fullscreen mode

Next, wrap your application or a specific part of your application with the Provider component to make the super context available to child components:

<Provider>
 {/* Your application or component tree */}
</Provider>
Enter fullscreen mode Exit fullscreen mode

Consuming the Super Context

To access the state and update it within a component, use the useStore hook provided by Super Context. The useStore hook takes a state selector function and returns an array with three items: the selected state, a setter function for the entire store, and an object of setters based on the items included in the initial state.

const MyComponent = () => {
  const [stateSelection, setStore, setters] = useStore(store => store);

  // Use the selected state and setters as needed
  return (
   {/* JSX for your component */}
   );
};
Enter fullscreen mode Exit fullscreen mode

In the example above, stateSelection represents the selected state from the Super Context. setStore is a function that allows you to update the entire store by providing a new state object. The setters object provides individual setter functions for each property of the state, enabling you to update specific properties directly.

Example: Counter

Let's start with a basic counter example to demonstrate how to use Super Context:

import React from 'react';
import { createSuperContext } from 'super-context';

const initialState = {
 counter: 0,
 // Add more properties as needed
};

const { Provider, useStore } = createSuperContext(initialState);

const CounterDisplay = () => {
  const [counter] = useStore((store) => store.counter);
  return <div>Counter: {counter}</div>;
};

const CounterButtons = () => {
  const [, setStore, setters] = useStore();  

  const increment = () => {
    setStore((prevStore) => ({ counter: prevStore.counter + 1 }));
  };

  const decrement = () => {
    setStore((prevStore) => ({ counter: prevStore.counter - 1 }));
  };

  return (
   <div>
   <button onClick={increment}>Increment</button>
   <button onClick={decrement}>Decrement</button>
   </div>
  );
};
Enter fullscreen mode Exit fullscreen mode
const App = () => {
  return (
    <Provider>
      <CounterDisplay />
      <CounterButtons />
    </Provider>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, we first import the necessary dependencies and create the super context using the createSuperContext function. We define an initial state with a counter property.

Next, we create two components: CounterDisplay and CounterButtons. The CounterDisplay component uses the useStore hook to select and access the counter property from the super context. It then renders the current value of the counter.

The CounterButtons component also uses the useStore hook. It receives the setStore function and the setters object as its return values. The increment and decrement functions use the setStore function to update the counter property in the super context by modifying the state.

Finally, in the App component, we wrap the CounterDisplay and CounterButtons components with the Provider component, making the super context available to them.
By leveraging the setStore function and the setters object provided by useStore, you have flexibility in updating the state based on your application's requirements.

Example: Shopping Cart

To illustrate the usage of Super-Context, let's consider a shopping cart example. Assume we have a shopping cart component that needs to access and update the cart items. We can define our super context and use it within the shopping cart component as follows:

import { createSuperContext } from 'super-context';

// Define the initial state
const initialState = {
  cartItems: [],
  cartId: null
};

// Create the super context
const { Provider, useStore } = createSuperContext(initialState);
Enter fullscreen mode Exit fullscreen mode

Wrap your application or the relevant part with the Provider component:

<Provider>
 {/* Your application or component tree */}
</Provider>
Enter fullscreen mode Exit fullscreen mode

Now, within the shopping cart component, we can access and update the cart items using the useStore hook:

const ShoppingCart = () => {
  const [cartItems, setStore, setters] = useStore(store => store.cartItems);

  const addToCart = (item: string) => {
    // use setStore
    setStore((prevState) => {
      return { cartItems: [ ...prevState.cartItems, item] };
    });

    // or
    setStore({ cartItems: [ ...cartItems, item ] })

    // or update the property directly by using a setter
    setters.setCartItems((cartItems) => [ ...cartItems, item ])
  };

  const clearCart = () => {
    // In the function above we used the current value in the setter
    // Alternatively we can also just pass a new value for the property
    setters.setCartItems([]);
  };

  // Rest of the component code
  return (
   <div>
      <h3>Shopping Cart</h3>
      {cartItems.map(cartItem => <div>Cart item name: {cartItem}</div>)}
      <button onClick={() => addToCart(`${Math.floor(Math.random() * 100)}`)}>Add Random Item</button>
      <button onClick={clearCart}>Clear Cart</button>
   </div>
  );
};
Enter fullscreen mode Exit fullscreen mode
const ShoppingCartInfo = () => {
  const [cartId, setStore, setters] = useStore(state => state.cartId);

  const updateCartId = () => {
    setters.setCartId(Math.floor(Math.random() * 100))
  }

  return (
    <div>
      <h3>Shopping Cart Id:</h3>
      <div>{cartId || 'no cart id'}</div>
      <button onClick={updateCartId}>Update cart Id</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
const App = () => {
  return (
    <Provider>
      <ShoppingCartInfo />
      <ShoppingCart />
    </Provider>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, the shopping cart component can access the cartItems state from the super context and update it using the setStore function. The setStore function allows you to update any property in the store by providing a partial state object, notice how we are not merging the prevState onto the returned object, this is because setStore does this for us under the hood. Additionally, the setters object provides individual setter functions for each property of the state, allowing you to update specific properties directly.

For instance, the clearCart function utilizes the setCartItems setter to clear the cart items by setting it to an empty array. This approach provides a convenient way to update specific properties of the state without modifying the entire store.

By leveraging the setStore function and the setters object provided by useStore, you have flexibility in updating the state based on your application's requirements.
Let's take a look at the rendering output for the above example:

Image description

As you can see, when we change either property in the state only the component that is consuming the changed state is re-rendered, reducing CPU usage overall and improving our app's performance.

Conclusion

Super Context is a lightweight npm package that simplifies state management in React applications by leveraging React's Context API. It provides an intuitive API, efficient state management using refs, setters, getters, and subscribers, and prevents unnecessary re-renders. By using Super Context, you can streamline your state management and improve the performance of your React applications.

In this tutorial, we covered the installation process, setting up your application, and demonstrated how to access and update the state using the useStore hook. We also showcased an additional way to update specific properties of the state using the setters object. With Super Context, you have the flexibility to manage your state with ease and optimize the rendering of your components.

Top comments (0)