i have created a userProvider component that fetches the user from the server on page load. Navigating between pages do not trigger a re-render, but when i refresh the page the user is fetched again. I want user data to persist across page-refresh.
important: user object contains sensitive information, such as email, historyEnabled, and role. As such I do not want to store data in local storage, as I have no way of verifying it. (I know i can setup an api endpoint for that, but that defeats the purpose of reducing load times)
here is the userProvider.tsx
"use client";
import React, { useContext, useEffect } from "react";
import { createContext, useState } from "react";
interface userProps {
id: string;
name: string;
about?: string;
email: string;
role: string;
historyEnabled: boolean;
}
interface userContextProps {
user: userProps | undefined;
isLoaded: boolean;
refreshUser: (user?: userProps) => void;
}
//context
const userContext = createContext<userContextProps>({
user: undefined,
isLoaded: false,
refreshUser: () => {},
});
//context provider component
const UserProvider = ({ children }: { children: React.ReactNode }) => {
//user State
const [user, setUser] = useState<userProps | undefined>();
const [isLoaded, setIsLoaded] = useState(false);
//get user from server on page load/reload
useEffect(() => {
fetchUser();
}, []);
//fetchUser Function
const fetchUser = async () => {
try {
setIsLoaded(false);
const res = await fetch("/api/user");
if (!res.ok) {
setUser(undefined);
throw new Error(`Error ${res.status} failed to fetch user`);
}
const user = await res.json();
setUser(user);
console.log("fetched user: ", user);
} catch (error) {
setUser(undefined);
console.log(error);
} finally {
setIsLoaded(true);
}
};
//function to update user state
const refreshUser = async (user?: userProps) => {
if (user) {
setUser(user);
console.log("user: ", user);
} else {
await fetchUser();
}
};
return (
<userContext.Provider value={{ user, refreshUser, isLoaded }}>
{children}
</userContext.Provider>
);
};
const useUser = () => {
const context = useContext(userContext);
if (!context.user) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
};
export { useUser, UserProvider };
here is how i use it in nextjs root layout:
import type { Metadata } from "next";
import "@/app/globals.css";
import { montserrat } from "@/app/ui/fonts";
import Dashboard from "@/app/(components)/dashboard/index";
import { Toaster } from "./(components)/reusable-ui/toaster";
import { UserProvider } from "./providers/UserProvider";
export const metadata: Metadata = {
title: "forage",
description: "forage is a blog site",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${montserrat.className} antialiased bg-item lg:bg-background`}
>
<UserProvider>
<Dashboard />
{children}
</UserProvider>
<Toaster />
</body>
</html>
);
}
my questions are:
- can i use useMemo to memoize the user data fetch request? does it persist data when I refresh my page?
- what is the common pattern for this problem? how does Auth0 or clerk handle this issue? I know clerk has const {user, isLoading} = useUser(); and I have tried to replicate it in my code.
I would be glad if any of my questions can be answered, thanks!
i have created a userProvider component that fetches the user from the server on page load. Navigating between pages do not trigger a re-render, but when i refresh the page the user is fetched again. I want user data to persist across page-refresh.
important: user object contains sensitive information, such as email, historyEnabled, and role. As such I do not want to store data in local storage, as I have no way of verifying it. (I know i can setup an api endpoint for that, but that defeats the purpose of reducing load times)
here is the userProvider.tsx
"use client";
import React, { useContext, useEffect } from "react";
import { createContext, useState } from "react";
interface userProps {
id: string;
name: string;
about?: string;
email: string;
role: string;
historyEnabled: boolean;
}
interface userContextProps {
user: userProps | undefined;
isLoaded: boolean;
refreshUser: (user?: userProps) => void;
}
//context
const userContext = createContext<userContextProps>({
user: undefined,
isLoaded: false,
refreshUser: () => {},
});
//context provider component
const UserProvider = ({ children }: { children: React.ReactNode }) => {
//user State
const [user, setUser] = useState<userProps | undefined>();
const [isLoaded, setIsLoaded] = useState(false);
//get user from server on page load/reload
useEffect(() => {
fetchUser();
}, []);
//fetchUser Function
const fetchUser = async () => {
try {
setIsLoaded(false);
const res = await fetch("/api/user");
if (!res.ok) {
setUser(undefined);
throw new Error(`Error ${res.status} failed to fetch user`);
}
const user = await res.json();
setUser(user);
console.log("fetched user: ", user);
} catch (error) {
setUser(undefined);
console.log(error);
} finally {
setIsLoaded(true);
}
};
//function to update user state
const refreshUser = async (user?: userProps) => {
if (user) {
setUser(user);
console.log("user: ", user);
} else {
await fetchUser();
}
};
return (
<userContext.Provider value={{ user, refreshUser, isLoaded }}>
{children}
</userContext.Provider>
);
};
const useUser = () => {
const context = useContext(userContext);
if (!context.user) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
};
export { useUser, UserProvider };
here is how i use it in nextjs root layout:
import type { Metadata } from "next";
import "@/app/globals.css";
import { montserrat } from "@/app/ui/fonts";
import Dashboard from "@/app/(components)/dashboard/index";
import { Toaster } from "./(components)/reusable-ui/toaster";
import { UserProvider } from "./providers/UserProvider";
export const metadata: Metadata = {
title: "forage",
description: "forage is a blog site",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${montserrat.className} antialiased bg-item lg:bg-background`}
>
<UserProvider>
<Dashboard />
{children}
</UserProvider>
<Toaster />
</body>
</html>
);
}
my questions are:
- can i use useMemo to memoize the user data fetch request? does it persist data when I refresh my page?
- what is the common pattern for this problem? how does Auth0 or clerk handle this issue? I know clerk has const {user, isLoading} = useUser(); and I have tried to replicate it in my code.
I would be glad if any of my questions can be answered, thanks!
Share Improve this question edited Feb 4 at 12:15 Zheng Jiawen asked Feb 4 at 12:09 Zheng JiawenZheng Jiawen 133 bronze badges1 Answer
Reset to default 1useMemo
does not keep your data on page refresh (full reload browser page and not re-render). Did you try leveraging caching for your API response?