admin管理员组

文章数量:1125034

Prerequisites

  • NextJS 14+
    • Using server action
    • Using app router
  • React Query 5+
  • Zustand+

Question

I wanted to come up with a design that takes advantage of the best of all the tech above.

Imagine we have a todos app with /todos page. What I wanted to happen is during the first page load (request from the client) The /todos page is server side rendered. Meaning the todos list on that page is rendered on the server side before being sent to the client.

When the client receives the page, I wanted the react query and zustand to take over. So example, say we have a refresh button on the said page, if we click that button, we fetch the latest todos data via server action using react query so the response is cached, then after getting the data, set it on zustand store.

Then moving forward, we will use data from the zustand store to populate the todos data on the todos table.

Been researching for a while now but I couldn't think of anything else other than passing the todos data from the server side page to the client todos component as a property, but this renders the todos data on the client side and not server side so, I am not even sure if my question is possible or is my intention makes any sense.

Any help is appreciated. Thanks in advance.

Prerequisites

  • NextJS 14+
    • Using server action
    • Using app router
  • React Query 5+
  • Zustand+

Question

I wanted to come up with a design that takes advantage of the best of all the tech above.

Imagine we have a todos app with /todos page. What I wanted to happen is during the first page load (request from the client) The /todos page is server side rendered. Meaning the todos list on that page is rendered on the server side before being sent to the client.

When the client receives the page, I wanted the react query and zustand to take over. So example, say we have a refresh button on the said page, if we click that button, we fetch the latest todos data via server action using react query so the response is cached, then after getting the data, set it on zustand store.

Then moving forward, we will use data from the zustand store to populate the todos data on the todos table.

Been researching for a while now but I couldn't think of anything else other than passing the todos data from the server side page to the client todos component as a property, but this renders the todos data on the client side and not server side so, I am not even sure if my question is possible or is my intention makes any sense.

Any help is appreciated. Thanks in advance.

Share Improve this question asked 2 days ago Jplus2Jplus2 2,5134 gold badges35 silver badges58 bronze badges 0
Add a comment  | 

2 Answers 2

Reset to default 0

we fetch the latest todos data via server action using react query so the response is cached, then after getting the data, set it on zustand store.

I don't understand why you're piping data from RQ to Zustand here. Just use the RQ data directly.

You need to create a RQ client on the server side, populate it with prefetched data, dehydrate and provide it. This way you can access to those data on both server & client side codes by RQ hooks and APIs.

e.g:

const USERS_LIST_RQ_KEY = ['users-list']

const UsersPage = async () => {
    const [queryClient] = useState(() => new QueryClient());
    const users = await apiClient.users.getList();

    await queryClient.setQueryData(USERS_LIST_RQ_KEY, users);

    return (
      <HydrationBoundary state={dehydrate(queryClient)}>
        <UsersTable />
      </HydrationBoundary>
    );
}

const UsersTable = () => {
  const { data } = useQuery({
    queryKey: USERS_LIST_RQ_KEY,
    queryFn: apiClient.users.getList
  });

  return (...)
}

Here you will have a server side rendered page, for further user list requests like for pagination and filtration, just do it on the client side. This way, you can handle the loading state better and avoid using server resources, since only the initial page load needs SSR, right?

Read more here: https://tanstack.com/query/latest/docs/framework/react/guides/ssr

And If you want to manage the data with Zustand instead of RQ, there are good examples in the docs on how to initialize the store on the server:

https://zustand.docs.pmnd.rs/guides/nextjs

  1. Zustand Store

    import { create } from 'zustand';

    const useTodosStore = create((set) => ({ todos: [], setTodos: (todos) => set({ todos }), }));

    export default useTodosStore;

  2. React Query Setup

    import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Hydrate } from '@tanstack/react-query';

    const queryClient = new QueryClient();

    function MyApp({ Component, pageProps }) { return ( <Component {...pageProps} /> ); }

    export default MyApp;

  3. todos Page (Server + Client) Server Action (Server-Side Rendering)

    export async function getTodos() { const res = await fetch('https://jsonplaceholder.typicode.com/todos'); if (!res.ok) throw new Error('Failed to fetch todos'); return await res.json(); }

Page Component

import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query';
import useTodosStore from '../stores/todosStore';
import { getTodos } from '../lib/todos'; // Server action

export async function getServerSideProps() {
  const queryClient = new QueryClient();

  // Prefetch todos on the server
  await queryClient.prefetchQuery(['todos'], getTodos);

  return {
    props: {
      dehydratedState: dehydrate(queryClient), // Pass preloaded data to client
    },
  };
}

export default function Todos() {
  const { todos, setTodos } = useTodosStore();

  // React Query fetches or uses preloaded data
  const { data: fetchedTodos } = useQuery(['todos'], getTodos, {
    onSuccess: (data) => {
      // Populate Zustand store on success
      setTodos(data);
    },
  });

  const refreshTodos = () => {
    // Fetch and update Zustand store
    getTodos().then((newTodos) => {
      setTodos(newTodos);
    });
  };

  const todosList = todos.length ? todos : fetchedTodos;

  return (
    <div>
      <h1>Todos</h1>
      <button onClick={refreshTodos}>Refresh</button>
      <ul>
        {todosList.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
}

Server-Side Rendering:

getServerSideProps uses getTodos() to fetch data server-side. The fetched data is passed to the client using React Query's hydration (dehydratedState). React Query:

useQuery ensures React Query manages the API calls and caches the data. On a successful fetch, the Zustand store (setTodos) is updated. Zustand Store:

The Zustand store holds the current todos state after React Query fetches data. Clicking "Refresh" bypasses React Query's cache (direct server call) and updates Zustand. Initial Data Flow:

On the first load, data comes from SSR via React Query. On subsequent interactions (e.g., refresh), React Query fetches new data and updates Zustand.

本文标签: nextjsNextJSReact QueryZustandStack Overflow