DEV Community

Elyor
Elyor

Posted on • Edited on

3 1 1 1

Efficient State Management in Next.js App Router with next-state-adapter

Managing state efficiently in a Next.js App Router environment can be challenging, especially when dealing with Server Components and Client Components. This is where next-state-adapter comes into play—providing a seamless solution for managing state across your Next.js application.

🚀 Why next-state-adapter?

next-state-adapter is a state management adapter specifically designed for Next.js App Router. It offers:

  • Seamless integration with Next.js (app/ directory)
  • Efficient server-side data handling for initial state hydration
  • Simplified state hydration in Client Components
  • Support for class components with an easy-to-use Higher-Order Component (HOC)
  • Works with various state management libraries like Mobx, Zustand, Jotai, Recoil, and others, giving you the flexibility to use your preferred state manager.

📦 Getting Started

First, install the package using npm or yarn:

npm install next-state-adapter
# or
yarn add next-state-adapter
Enter fullscreen mode Exit fullscreen mode

🛠 Setting Up next-state-adapter

1. Create the Root Store and Provider

Start by setting up the root store and necessary hooks for state management:

// ~/store/config.ts
'use client';

import {RootStore} from "@/store/root";
import {createProvider, useStore} from "next-state-adapter";

const makeStore = () => new RootStore();

export const useAppStore = useStore.withTypes<RootStore>();
export const useAppStoreHydration = useStoreHydration.withTypes<RootStore>();
export const StoreProvider = createProvider(makeStore);
Enter fullscreen mode Exit fullscreen mode
// ~/store/withStore.ts
import {withStore as withStoreHoc} from "next-state-adapter";

export const withStore = withStoreHoc.withTypes<RootStore>();
Enter fullscreen mode Exit fullscreen mode

2. Wrap Your Application with the Store Provider

Now, wrap your application in the StoreProvider component inside layout.tsx:

// ~/app/layout.tsx

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <body>
                <StoreProvider>
                    {children}
                </StoreProvider>
            </body>
        </html>
    );
}
Enter fullscreen mode Exit fullscreen mode

3. Use the Store in a Client Component

Let’s create a Todo List component that fetches initial data from the server and hydrates the client-side store:

// ~/todos/list.tsx
'use client';

const TodoList = ({ initialTodos }: { initialTodos: Todo[] }) => {
    const { todos } = useAppStoreHydration((store) => {
        store.todos.init(initialTodos);
    });

    return (
        <ul>
            {todos.todos.map((todo) => (
                <li key={todo.id}>{todo.title}</li>
            ))}
        </ul>
    );
};
Enter fullscreen mode Exit fullscreen mode

4. Use the Component in a Server Component

Now, fetch the initial state on the server and pass it to TodoList:

// ~/app/todos/page.tsx
export default async function Todos() {
    const initialTodos = await fetchTodos(); // Fetching initial data on server side

    return <TodoList initialTodos={initialTodos} />;
}
Enter fullscreen mode Exit fullscreen mode

5. Support for Class Components

If you're using class components, next-state-adapter provides an easy way to inject the store via HOC:

type Props = {
    store: RootStore;
    initialUsers: User[];
};

class Users extends Component<Props> {
    render() {
        const { store } = this.props;
        const users = store.users.users;

        return (
            <div>
                {users.map((user) => (
                    <div key={user.id}>{user.id}</div>
                ))}
            </div>
        );
    }
}

// Inject store and hydrate with initialUsers
typescript
export const UsersList = withStore(Users, (store, props) => {
    store.users.init(props.initialUsers);
});

// Use in a server component
export default async function UsersPage() {
    const initialUsers = await fetchUsers();
    return <UsersList initialUsers={initialUsers} />;
}
Enter fullscreen mode Exit fullscreen mode

🎯 Final Thoughts

The next-state-adapter simplifies state management in Next.js App Router applications by providing a structured, optimized. Whether you are working with functional components, class components, or server-side data hydration, this adapter ensures a smooth developer experience.

It also supports a wide range of state management libraries like Mobx, Zustand, Jotai, Recoil, and others, so you can integrate it with your preferred solution effortlessly.

Give it a try in your Next.js App Router project and experience a more efficient way to manage state!

🚀 Check out the official documentation: next-state-adapter docs

🚀 Link to npm: next-state-adapter npm

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (2)

Collapse
 
oqiljon_dadaxanov_2322ce5 profile image
Oqiljon Dadaxanov

cool :)

Collapse
 
azeek profile image
Azeek

Informative and straight to the point

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay