jherr / nextjs13-state-zustand

Code for Did NextJS 13 Break State Management?
63 stars 18 forks source link

access to zustand store in server component #3

Open adadvar opened 10 months ago

adadvar commented 10 months ago

i have acces to store in server compoent and client component in client component when update the state its ok and can access to updated store but in server component i have access just old and first value of state and when update state in client component , i cant see updated store in server compoent

shrekuu commented 8 months ago

@adadvar Hahah~ I have this problem too. Here are some options:

Good option: like what Jack does in the repo, useStore.setState in the component function body of StoreInitializer.tsx, and you render this component before any other client-side components, during the first render of other client-side components, you get the stored data you passed down from the server. Since the client-side components are also pre-rendered on the server, so the zustand stores become singletons on the server. The more store you pass down, the more RAM they eat on the server-side. Since JavaScript is single-threaded, for each request, you just have to make sure the store state contains data related to current request, or at its initial "empty" state. The client-side component hydration will work correctly since data like this const bearStore = useBearStore() gets the latest data during the pre-render on the server and the first render on the client.

Bad option: If you wrap all the useStore.setState in UseEffect, then the store data will be available after the first render. You will still see components with no store data, and a lot of mismatch errors in browser console. Then after we set store state in the useEffect, these components who uses store data get updated during the second render. This is not a good user experience.

Sometimes you-just-have-to option:

You add this as a global component NoSSR.tsx:

import dynamic from 'next/dynamic';
import React from 'react';

const NoSsr = ({ children }: { children: React.ReactNode }) => <React.Fragment>{children}</React.Fragment>;

export default dynamic(() => Promise.resolve(NoSsr), {
  ssr: false, // This is important to turn off server side rendering(client-side component pre-rendering actually)
});

Then you turn off pre-rendering for client-side components like this:

export default async function BearRsc() {

  return (
    <NoSsr>
      <BearCsc />
    </NoSsr>
  );
}

This BearCsc component is not rendered (or pre-rendered) on the server side now.

Conclusion: This is the nature of Next.js + state management I believe. There is no silver bullet, just trade-offs.

jherr commented 8 months ago

Agreed that there is no silver bullet. By my current solution is to create a store, initialized with state passed in from an RSC and then to passed to client components as context. This also avoids the parallel problem because you should never have singleton external stores with the App Router. If you need the state in an RSC then fetch it directly in the RSC (as the platform intends) or promote it to a client component and get it from the context. Client components still render on the server.

shrekuu commented 8 months ago

Yes great! @jherr I see your latest solution is here already(context + zustand):

And these docs and example are cool too!

❤️

jherr commented 8 months ago

Yes, their example is better than mine (currently) because it holds just the store in context and uses useStore which is preferred. I'll fix mine up. Apparently returning a hook from a context is bad for React Forget because that hook function reference could change (it won't, but it could) so that example from the guides is better.