So I am building a Frontend using NextJs 13 app directory. I have the login page at app/page.tsx. The code for it is below.
"use client";
import { useMediaQuery } from "@mantine/hooks";
import { useForm } from "@mantine/form";
import mediaQueryConstants from "@/constants/mediaQuerConstants";
import { LoginApi } from "@/api/auth";
import { useEffect, useState } from "react";
import LoginScreen from "@/Screens/Login/login";
import userDataStore from "@/Store/userData";
export default function LoginComponent() {
const form = useForm({
initialValues: {
email: "",
password: "",
},
validate: {
email: (value) => (/^S+@S+$/.test(value) ? null : "Invalid email"),
password: (value) =>
value.length > 5
? null
: "Please enter password with length greater than 5",
},
});
const setUserData = userDataStore((state) => state.setUserData)
const userData = userDataStore((state)=>state)
const [isLoading, setLoading] = useState(false);
const isLargeScreen = useMediaQuery(mediaQueryConstants.isLargeScreen);
const handleSubmit = async (email: string, password: string) => {
setLoading(true);
const loginAttemptData = await LoginApi(email, password);
if (!loginAttemptData.isSuccess)
form.setErrors({
[loginAttemptData.data.incorrectField]: loginAttemptData.message,
});
if (loginAttemptData.isSuccess) setUserData(loginAttemptData.data)
setLoading(false);
};
useEffect(()=>console.log(userData),[userData])
return (
<LoginScreen
handleSubmit={handleSubmit}
form={form}
isLoading={isLoading}
isLargeScreen={isLargeScreen}
/>
);
}
The state is being updated here as I am logging it and I can see it in the console.
The userDataStore code is here.
import { UserData, UserStore } from "@/types";
import { create } from "zustand";
// import { persist } from "zustand/middleware";
const userDataStore = create<UserStore>()((set) => ({
tenantId: null,
userId: null,
accessToken: null,
refreshToken: null,
setUserData: (userData: UserData) => set(userData),
}));
export default userDataStore;
Now when I try to navigate to admin/page.tsx which is a server-side component the state does not persist and I see null data here is the admin.page.tsx code
import userDataStore from "@/Store/userData"
import { GetUsers } from "@/api/user"
export default async function AdminDashboard() {
await GetUsers()
return (
<section>
Hello Admin
</section>
)
}
Also, I have the business logic to check if the state is set or not in layout.tsx if the state is not set send the user to login page
'use client'
import './globals.css'
import { usePathname } from 'next/navigation'; // Import the useRouter hook
import { useRouter } from 'next/navigation'
import Provider from './provider'
import MainShell from '@/Components/AppShell'
import { useEffect, useState } from 'react';
import userDataStore from '@/Store/userData';
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
const router = useRouter()
const [tenantId,userId] = userDataStore((state)=>([state.tenantId,state.userId]))
// Use the state to track the hidden value of
// app shell
const [hidden, setHidden] = useState(true);
useEffect(()=>console.log(tenantId,userId),[tenantId,userId])
// Update the hidden value when the route changes
useEffect(() => {
setHidden(pathname === '/');
if(!tenantId || !userId) router.replace("/")
}, [pathname,tenantId,userId]);
return (
<html lang="en">
<body>
<Provider>
<MainShell hidden={hidden}>
{children}
</MainShell>
</Provider>
</body>
</html>
)
}
I have tried everything from making admin/page to client side component and removing the logic from layout.tsx but nothing seems to be working please let me know what the bug is or if there is any alternate way I should try to implement it.
2
Answers
So I did some research and came to a conclusion that zustand doesnt work with server components. Here is the response from zustand github. The workaround I did was using http cookies for state management. So each time a users logs in I am sending access and refresh tokens. I check if the token is set or not in layout.tsx. If you want to utilize zustand you will have to use client component instead.
Zustand
is a client side library. You can use it along with app-directory for sharing state between client side components. Check out example here. However, you can not use client side library on server side – neither for Server Components nor for API routes/route handlers.