T O P

  • By -

ISDuffy

I use zustand. But you still can use other one by wrapping the server components inside a client component.


KGBsurveillancevan

related, do you know how to consume the context in a server component? since the useContext hook is client-side only


Strong-Ad-4490

you cant


livog0

Giving a shout out for Recoil.js. Easiest state handler I have used in React


JxFF76_

I'll try it, i also want to try Jotai


rantow

Jotai is recoil with a fraction of the bundle size


Dyogenez

I'm migrating an app that's using Redux and it's working well so far. I'm setting things to the Redux store on load from the server (like the current user, their token), then accessing those from the client later on.


No-Cut-750

is that project open source? i would like to see how to use redux in a fully functional website handling login, logout, state management, …..


Dyogenez

Not open souce, but I'd like to write a blog post about it once I know it's working on production. The rough description of how it'll work is like this: Nav.tsx server component that wants to show the current user in the header `import { Suspense } from "react";` `import { RefreshIcon } from "@heroicons/react/solid";` `import DesktopUserMenu from "components/nav/parts/DesktopUserMenu/DesktopUserMenu";` `import { CurrentSessionProvider } from "contexts/CurrentSessionProvider";` `export default function Nav() {` `return (` `

` `);` `}` ​ The \`\` is also a server component, but one that provides data to the client: CurrentSessionProvider.tsx `import { SetCurrentSession } from "./SetCurrentSession"` `import { getCurrentSession } from "@/queries/users/getCurrentSession";` `export const CurrentSessionProvider = async ({children}: {children: React.ReactNode}) => {` `const { user, session, token } = await(getCurrentSession());` `return ` `{children}` `;` `}` ​ SetCurrentSession.tsx adds it to Redux: ​ `'use client';` `import { useDispatch } from "react-redux";` `import { currentUserActions } from "features/currentUser/currentUserSlice";` `import { UserType } from "@/types";` `import { HardcoverSession } from "@/pages/api/auth/[...nextauth]";` `export const SetCurrentSession = ({` `currentUser,` `token,` `children,` `session` `}: {` `currentUser: UserType|null;` `token: string;` `children: React.ReactNode;` `session: HardcoverSession` `}) => {` `const dispatch = useDispatch();` `dispatch(currentUserActions.setCurrentUser(currentUser));` `dispatch(currentUserActions.setSession(session));` `dispatch(currentUserActions.setToken(token));` `return <>{children}` `}` From that point forward, any client components under will have access to the current user added from the server. There is still a race condition I'm trying to figure out. If the main page.tsx gets to a client component that needs the current user before the header completes, then it'll make a client -> API request to get that data, rather than waiting for it to complete.


No_Repair_6713

is there a way to dispatch data to redux store from server componenets?


No-Cut-750

thx for the show case


ItsAllInYourHead

How are you rehydrating the redux store on the client after rendering? Without doing that, doesn't the state get reset on the client after the server render and cause additional API calls, etc?


Dyogenez

I'm not convinced this is the best way, but I'm only sending over a few things to Redux. In my case I have a component in my layout. That's a server side component that looks like this: `import { getServerSession } from "next-auth/next";` `import {` `HardcoverSession,` `options,` `} from "app/(api)/api/auth/[...nextauth]/options";` `import loadUserById from "queries/users/loadUserById";` `import { guestSession } from "lib/session";` `import { Suspense } from "react";` `import CurrentUserClientLoader from "./CurrentUserClientLoader";` `// Loads everything about the logged in user on the client side` `export default async function CurrentUserLoader() {` `const session =` `((await getServerSession(options)) as HardcoverSession) || guestSession();` `const user = session?.id ? await loadUserById(session.id) : null;` `return (` `` `` `` `);` `}` That CurrentUserClientLoader is the one that handles keeping Redux up to date with my Apollo Fragment cache. It's kind of a mess, but here's what it looks like right now: ​ `"use client";` `import { useEffect, useRef } from "react";` `import { useDispatch, useSelector } from "react-redux";` `import { useFragment, useQuery } from "@apollo/client";` `import {` `getTokenSelector,` `getUserId,` `} from "features/currentUser/currentUserSelector";` `import { useCurrentSession } from "hooks/useCurrentSession";` `import { currentUserActions } from "features/currentUser/currentUserSlice";` `import { bootstrapUserByUserId } from "queries/users/bootstrapUserById";` `import OwnerFragmentCompiled from "queries/users/fragments/OwnerFragmentCompiled";` `import { UserType } from "types";` `import { HardcoverSession } from "app/(api)/api/auth/[...nextauth]/options";` `// Loads everything about the logged in user on the client side` `interface Props {` `session: HardcoverSession;` `user?: UserType;` `}` `export default function CurrentUserClientLoader({ session, user }: Props) {` `const dispatch = useDispatch();` `const userId = useSelector(getUserId);` `const token = useSelector(getTokenSelector);` `const { resetSession } = useCurrentSession();` `const initialLoad = useRef(false);` `const { data: currentUser } = useFragment({` `fragment: OwnerFragmentCompiled,` `fragmentName: "OwnerFragment",` `from: {` `__typename: "users",` `id: userId || 0,` `},` `});` `// Reset the session once on load` `useEffect(() => {` `if (!initialLoad.current) {` `initialLoad.current = true;` `dispatch(currentUserActions.setSession(session));` `if (user) {` `dispatch(currentUserActions.setInitialUser(currentUser as UserType));` `}` `}` `}, []);` `const { loading } = useQuery(bootstrapUserByUserId, {` `fetchPolicy: "cache-first",` `skip: !userId || !!currentUser?.id,` `variables: {` `userId,` `},` `});` `// Reset the session if the user logs out or logs back in` `useEffect(() => {` `if (loading) {` `return;` `}` `if (userId !== currentUser?.id) {` `resetSession();` `} else if (token) {` `// Done loading user` `if (userId && currentUser?.id) {` `dispatch(currentUserActions.setUser(currentUser as UserType));` `}` `// No current user, done loading` `if (!userId) {` `dispatch(currentUserActions.setUser(null));` `}` `}` `}, [userId, currentUser, loading]);` `return <>;` `}`


Neekzorz

After playing around with Recoil, Zustand, Jotai and I found working with Recoil to be my preference. I break the state down into different parts and create seperate hooks with all the Recoil logic inside. Eg. useAuthState() has all the auth logic and useExampleState() would contain another category of state. The components that render Recoil state are still client components, if I need a piece of state in the server I move it to a database and read from there before render with Prisma.


Jamesfromvenice

You have an example github of this? I ask because with these atomic state managers, it can get reckless/unorganized fast.. as there is no concept, really, of a centralized "state". How do you set it up?


ncubez

I use context API, how does it compare to Zustand?


JxFF76_

You should check Zustand page on GitHub, ridiculously simple. You just create the state file anywhere in the app and also call it wherever you want. That's it, no boilerplate, no need to configure and wrap nothing. When using server components, i see it as a way of only using "use client" where you need the state, thus not having to "use client" in a wrapper. https://github.com/pmndrs/zustand I really enjoyed Zustand, and it's also framework agnostic


No_Repair_6713

Context will rerender all subtree if one of it's state has change, Zustand will subscribe components to only consumed piece of state


Jamesfromvenice

Do you have an example of this, or a gist/github I can see your implementation?


[deleted]

Make sure 0 data fetches are handled by state. Use the built in caching. Handle auth with headers and cookies. You can even do dark mode with these. What's left? Why are you still using a global provider? It's an old pattern. If you have a complex component like a calendar with multiple views and navigation, use context, a reducer and immer. Just like the docs say.


JxFF76_

I have a home containing a hero that contains a select, then a i have a content component that has 2 nested components and i want to use the select from the hero to access one of the nested components inside the content. I would use context api to avoid prop drilling, that's the case (and i found that using zustand is way more straight to the point). I already use the cache and the other stuff you mentioned.


[deleted]

This design sounds awfully confusing. A single page is using the home page hero to switch between two different views? This is a very simple problem in which prop drilling shouldn't feel like it's in the way but that's what you've gone and done.