I have build my authentication system with next-auth. The data
object returned from the useSession
hook returns the ID, that I then need to pass into a fetchUser
function, so that I may acquire the user details and set that to a global state so the user with details can be shared throughout all my child components.
My MainLayout component wraps all the children that need access to the user in state
export const UserContext = createContext<{ user: UserWithDetails | null }>({
user: null,
})
export const MainLayout: React.FC<Props> = ({ children }) => {
const { data } = useSession()
const [user, setUser] = useState<null | UserWithDetails>(null)
const [isLoading, setIsLoading] = useState<boolean>(false)
const router = useRouter()
const fetchUser = async (id: unknown) => {
setIsLoading(true)
const res = await fetch(PATHS.API.userById(id))
const { data, success, errorMessage }: ResForUserWithDetails =
await res.json()
if (!success) {
console.warn(errorMessage)
}
setUser(data)
setIsLoading(false)
}
useEffect(() => {
if (data && !user) fetchUser(data.id)
}, [data, user])
return (
<UserContext.Provider value={{ user }}>
<Box h="100vh" bg="primary.100">
<Header />
{isLoading ? <Loader /> : children}
<Nav currentRoute={router.pathname} options={NAV_MENU_OPTIONS} />
</Box>
</UserContext.Provider>
)
}
The problem with this execution is the createContext is always initially set to null when the component mounts. As I toggle between children components the user is always refetched. My end goal is to set it once and leave it alone unless a refresh is triggered. If a user is accessing MainLayout they have already been authenticated and a valid JWT is present.
I know this question may be loaded but there is there any convention which supports setting the user to state only once instead of on every render.
This is my AUTH system and its application.
export const RequireAuth = ({
children,
}: {
children: ReactElement<any, any>
}) => {
const router = useRouter()
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push(PATHS.LOGIN)
},
})
if (status === 'loading') {
return <Loader />
}
return children
}
__app.tsx
const CustomComponent: NextPage = () => {
if (OMIT_LAYOUTS[router.pathname]) {
return <Component {...pageProps} />
} else {
return (
<MainLayout>
<Component {...pageProps} />
</MainLayout>
)
}
}
return (
<SessionProvider session={session} refetchOnWindowFocus={true}>
<ChakraProvider theme={theme}>
{Component.auth ? (
<RequireAuth>
<CustomComponent />
</RequireAuth>
) : (
<CustomComponent />
)}
</ChakraProvider>
</SessionProvider>
)
}