State Scope with Providers - Next.js with Jotai

alexteng

Alex T.

Posted on April 19, 2024

State Scope with Providers - Next.js with Jotai

In the React state management, Jotai emerges as a minimalist and modern library, offering an elegant solution to handle atom-based state with simplicity and precision. This article delves into the practical use of Jotai to create isolated state scopes within a React application, utilizing the concept of Providers. We'll explore how to structure components for shared or independent state management, walking through a hands-on example that illustrates the power and flexibility of Jotai in a React project.

Isolating Component States with Jotai Providers

When building complex applications in React, it's common to encounter scenarios where components need to share state or maintain their own independent state. Jotai, a state management library, provides a straightforward and efficient way to handle these requirements. Let's look at a practical example to understand how Jotai achieves this with its Provider pattern.

In the code snippet below, we have a SharedComponent that is utilized multiple times within two separate contexts created by Provider.

// page.tsx
import Provider from './path-to-provider'; // Ensure to import the Provider from its file
import SharedComponent from './shared-component'; // Ensure to import SharedComponent

const Page = () => {
  return (
    <div>
      <Provider>
        <SharedComponent /> {/* C1 */}
        <SharedComponent /> {/* C2 */}
      </Provider>
      <Provider>
        <SharedComponent /> {/* C3 */}
        <SharedComponent /> {/* C4 */}
      </Provider>
    </div>
  );
};

export default Page;
Enter fullscreen mode Exit fullscreen mode

Each Provider in the code above creates a unique context for state management. Consequently, SharedComponent instances within the same Provider share a common state, while those under different Provider instances maintain their own state, isolated from others.

Here's how SharedComponent connects to the state using Jotai:

// shared-component.tsx
import { atom, useAtom } from 'jotai';

const countAtom = atom(0); // This atom holds the state for our component

const SharedComponent = () => {
  const [count, setCount] = useAtom(countAtom); // This hook connects our component to the atom's state

  return (
    <div>
      Shared component
      <div>{count}</div>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        +1
      </button>
    </div>
  );
};

export default SharedComponent;
Enter fullscreen mode Exit fullscreen mode

Clicking the "+1" button in any instance of SharedComponent (C1, C2, C3, C4) will result in:

  • Simultaneous increment of count in C1 and C2, as they share the same state within a single Provider.
  • Simultaneous increment of count in C3 and C4 for the same reason.
  • Independent state changes between C1/C2 and C3/C4 due to the distinct Provider wrapping, creating isolated states.

This example demonstrates the use of Provider to create separate "contexts" or "scopes" of state within a React application, facilitating both shared and independent state management across the component hierarchy.

Structuring Shared and Isolated States with Jotai

In this section, we'll further explore how to share or isolate state between components in a React application using Jotai atoms and Providers.

We begin by declaring a shared atom in a separate file:

// atoms.ts
"use client";
import { atom } from 'jotai';

export const sharedCountAtom = atom<number>(0);
Enter fullscreen mode Exit fullscreen mode

By exporting the sharedCountAtom, we make it available to any component that needs to tap into this shared state.

The main page's component structure is as follows:

// page.tsx
import { Provider } from 'jotai';
import ComponentC from './ComponentC';
import ComponentD from './ComponentD';

const Page = () => {
  return (
    <div>
      <Provider>
        <ComponentC /> {/* Component C1 */}
        <ComponentD /> {/* Component D1 */}
      </Provider>
      <ComponentC /> {/* Component C2 */}
      <ComponentD /> {/* Component D2 */}
    </div>
  );
};

export default Page;
Enter fullscreen mode Exit fullscreen mode

ComponentC and ComponentD are functionally identical and use the sharedCountAtom for state management. The context in which they are rendered, inside or outside a Provider, determines their state sharing behavior:

  • ComponentC1 and ComponentD1 inside a Provider share the same atom state.
  • ComponentC2 and ComponentD2 outside a Provider share a different instance of the atom state, isolated from ComponentC1 and ComponentD1.

Here are the component implementations:

// ComponentC.tsx
"use client";
import { useAtom } from 'jotai';
import { sharedCountAtom } from './atoms';

const ComponentC = () => {
  const [count, setCount] = useAtom(sharedCountAtom);
  return (
    <div className='border-2 w-60 h-60'>
      Component C
      <div>Count: {count}</div>
      <button
        className="border-1 border-black bg-blue-300 text-white hover:bg-yellow-200 p-3"
        onClick={() => setCount((prev) => prev + 1)}
      >
        +1
      </button>
    </div>
  );
};

export default ComponentC;
Enter fullscreen mode Exit fullscreen mode
// ComponentD.tsx
"use client";
import { useAtom } from 'jotai';
import { sharedCountAtom } from './atoms';

const ComponentD = () => {
  const [count, setCount] = useAtom(sharedCountAtom);
  return (
    <div className='border-2 w-60 h-60'>
      Component D
      <div>Count: {count}</div>
      <button
        className="border-1 border-black bg-blue-300 text-white hover:bg-yellow-200 p-3"
        onClick={() => setCount((prev) => prev + 1)}
      >
        +1
      </button>
    </div>
  );
};

export default ComponentD;
Enter fullscreen mode Exit fullscreen mode

Note: It is crucial to include the directive "use client" in all components to ensure proper client-side state management, preventing potential issues.

In summary, ComponentC and ComponentD may import the same atom from atoms.ts, but their state sharing is controlled by the use of Provider. This setup exhibits the adaptability of Jotai in managing state across various scopes within a React application.

Conclusion

The React ecosystem offers various approaches to state management, each with its own set of trade-offs. Jotai, with its minimalist and atom-based approach, provides a powerful yet simple solution for developers to control state scope within their applications. By leveraging Providers, developers can easily create shared or isolated state contexts, ensuring that components behave predictably and maintain clean separation of concerns. As we've seen in the examples above, Jotai's flexibility and ease of use make it an excellent choice for React developers looking to streamline their state management practices.

Reference:

The first example is inspired from this article - # Jotai V2 Notes 02 — Store & Provider

💖 💪 🙅 🚩
alexteng
Alex T.

Posted on April 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related