DEV Community

Elyor
Elyor

Posted on • Edited on

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

Top comments (2)

Collapse
 
oqiljon_dadaxanov_2322ce5 profile image
Oqiljon Dadaxanov

cool :)

Collapse
 
azeek profile image
Azeek

Informative and straight to the point