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);
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>
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 */}
);
};
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>
);
};
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:
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:
<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:
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>
);
};
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>
)
}
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:
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)