Super Context: Unleashing Blazing Performance for Your ReactJS Apps!
Tags: react, webdev, javascript, opensource
Published: 2023-07-25 17:16:30

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.

```javascript
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);
```

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

```javascript
<Provider>
 {/* Your application or component tree */}
</Provider>
```


### 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.

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

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

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:

```javascript
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>
  );
};
```

```javascript
const App = () => {
  return (
    <Provider>
      <CounterDisplay />
      <CounterButtons />
    </Provider>
  );
};
export default App;
```

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:

```javascript
import { createSuperContext } from 'super-context';

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

// Create the super context
const { Provider, useStore } = createSuperContext(initialState);
```
Wrap your application or the relevant part with the Provider component:

```javascript
<Provider>
 {/* Your application or component tree */}
</Provider>
```
Now, within the shopping cart component, we can access and update the cart items using the `useStore` hook:

```javascript
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>
  );
};
```

```javascript
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>
  )
}
```

```javascript
const App = () => {
  return (
    <Provider>
      <ShoppingCartInfo />
      <ShoppingCart />
    </Provider>
  );
};
export default App;
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iivwl1n4fbbgkdvogtmz.gif)

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.