I have a UserProvider that handles authentication for my Next.js application:
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [token, setToken] = useState('');
const [userLoading, setUserLoading] = useState(true);
const router = useRouter();
const { data, error } = useSWR('/api/user', fetchUser);
const publicRoutes = [
'/',
'/login',
'/docs',
];
const isPublicRoute =
publicRoutes.includes(router.pathname);
// Sets user data if the user is authenticated
useEffect(() => {
if (data?.user) {
console.log('[UserProvider] user retrieved');
setUser(data.user);
console.log('[UserProvider] user data set', user);
setUserLoading(false);
} else if (data && !data.user && !isPublicRoute) {
setUser(null);
setUserLoading(false);
console.log('[UserProvider] user not retrieved');
if (typeof window !== 'undefined') {
router.push('/login');
}
} else if (error) {
setUserLoading(false);
console.error('[UserProvider] error', error);
if (typeof window !== 'undefined') {
router.push('/login');
}
}
}, [data, error]);
return (
<UserContext.Provider
value={{
user,
userLoading,
}}
>
{children}
</UserContext.Provider>
);
}
However, when I try to access the user value from a hook I’ve created to create my Apollo Client, the user value won’t update at all.
const useApolloClient = () => {
const { user, userLoading } = useContext(UserContext);
// Initiate client
const [client, setClient] = useState(() => {
return new ApolloClient({
link: new HttpLink({
uri: '/graphql',
}),
headers: {
'content-type': 'application/json',
Authorization: user?.token ? `Bearer ${user.token}` : '',
},
cache: new InMemoryCache(),
});
});
useEffect(() => {
if (user) {
const httpLink = createHttpLink({
uri: '/graphql',
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.error(`[Network error]: ${networkError}`);
});
const authLink = setContext((_, { headers }) => {
// Ensure user data is available and valid
if (user) {
console.log('[ApolloClient] Token ', user.token);
}
if (!user) {
console.error('[ApolloClient] User data is incomplete or not available.');
return { headers };
}
return {
headers: {
...headers,
'content-type': 'application/json',
Authorization: user?.token ? `Bearer ${user.token}` : '',
},
};
});
const apolloClient = new ApolloClient({
link: authLink.concat(errorLink).concat(httpLink),
cache: new InMemoryCache(),
});
setClient(apolloClient);
}
}, [user, userLoading]);
return client;
};
export default useApolloClient;
The ApolloClient is definitely wrapped within the UserProvider, so I don’t think passing the data is an issue, but surely I’m doing something dumb. I currently don’t store any user data in the Store/StoreProvider if that helps.
const App = ({ Component, pageProps }) => {
const router = useRouter();
const client = useApolloClient();
return (
<UserProvider>
<ApolloProvider client={client}>
<ThemeProvider theme={theme}>
<StoreProvider store={store}>
<GlobalStyle />
<Component {...pageProps} />
</StoreProvider>
</ThemeProvider>
</ApolloProvider>
</UserProvider>
);
};
Here’s the initialized UserContext:
export const UserContext = createContext({ login: null, user: null, token: null, userLoading: true, setUser: null});
I’ve tried re-writing the UserProvider, re-writing the ApolloProvider, and changing the dependencies in every respective useEffect
hook, but so far, nothing seems to pass the user
data through. The most similar issues seem to relate to mutating the state instead of setting new state properly via useState
but I don’t think I’m mutating anything here.
pandafiesta is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.