Hopefully I can provide enough context where this is clear. I have a Next.js app that is using ApolloClient. I have _app.js
wrapped in the withApolloHOC component from withApollo.js file below so the whole app should have access to Apollo (Which it seems to).
The problem is that on line 21 of the withApollo.js file an auth header is set from a cookie that is set from the signed-in.js file. For whatever reason the query works on the signed-in page but NOT on any other page…so what happens is:
- The user signs in and is redirected to sgined-in page
- The signed-in page sets the cookie on line 43
setToken(result.idToken, result.accessToken);
- If auth was successful the query is made on line 32
const [getDbUser, { loading, error, data }] = useLazyQuery(GET_USER_BY_EMAIL);
(when getDbUser is executed it works) - The signed-in page then redirects to index.js
- A component SetUser.js in index.js tries to run the same query except it uses
userQuery
instead ofuserLazyQuery
and fails because the auth header is not set to the cookie. - If I refresh index.js the same
userQuery
that just failed to set the auth header now properly sets the auth header.
My question…or what I’m trying to figure out is why is the auth properly set at step 3 and 6 but not at step 5?
withApollo.js
import { useMemo } from "react";
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import withApollo from "next-with-apollo";
import { ApolloProvider } from "@apollo/client";
import Cookie from 'js-cookie';
import { endpoint } from '../config';
let apolloClient;
function createApolloClient() {
const isBrowser = typeof window !== 'undefined';
const authCookie = (isBrowser) ? Cookie.get('idToken') : '';
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser,
link: new HttpLink({
uri: endpoint, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
headers: {
authorization: authCookie
}
}),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState });
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
export default function withApolloHOC(WrappedComponent) {
return (props) => {
const apolloClient = useApollo(props.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
<WrappedComponent {...props} />
</ApolloProvider>
)
}
}
signed-in.js
import React, { useEffect, useState } from 'react';
import Router from 'next/router';
import { useLazyQuery } from '@apollo/client';
import { setToken } from '../../lib/auth';
import { getUserFromToken } from '../../lib/auth';
import { parseHash } from '../../lib/auth0';
import { GET_USER_BY_EMAIL } from '../../sharedQueries/users';
// REDUX
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { updateCurrentUser } from '../../lib/redux/actions/userActions';
// Redirect function
function redirect(url) {
console.log('performing redirect: ' + url)
if (url) {
Router.push(url)
} else {
Router.push('/');
}
}
function loadingView() {
return null;
}
function signIn({ url, updateCurrentUser }) {
const [authUser, setAuthUser] = useState();
const isBrowser = typeof window !== 'undefined';
const [getDbUser, { loading, error, data }] = useLazyQuery(GET_USER_BY_EMAIL);
// Only run in browser
if (!isBrowser) return null;
useEffect(() => {
parseHash((err, result) => {
// Need to return on error or else everything FAILS
if (err) return null;
// Set Tokens and auth user state
setToken(result.idToken, result.accessToken);
setAuthUser(getUserFromToken(result.idToken));
});
}, [authUser]); // Only re-run the effect changes
if (!authUser || loading) return loadingView();
// Fetch DB user if email verified
if (authUser.email_verified && !error && !data) {
getDbUser({
variables: {
email: authUser.email
}
});
}
const dbUser = (data) ? data.user : undefined;
updateCurrentUser({ ...authUser, dbUser });
redirect(url);
return null;
}
const mapDispatchToProps = dispatch => {
return {
updateCurrentUser: bindActionCreators(updateCurrentUser, dispatch)
}
}
export default connect(
null,
mapDispatchToProps
)(signIn);
SetUser.js
import React from 'react';
import { getUserFromLocalCookie } from '../lib/auth';
import { useQuery } from '@apollo/client';
import { GET_USER_BY_EMAIL } from '../sharedQueries/users';
// REDUX
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { updateCurrentUser } from '../lib/redux/actions/userActions';
function setUser({ updateCurrentUser }) {
const loggedUser = (typeof window !== 'undefined')
? getUserFromLocalCookie()
: null;
if (loggedUser) {
const { loading, error, data } = useQuery(GET_USER_BY_EMAIL, {
variables: {
email: loggedUser.email
}
});
console.log(error) // On first run I get an error, but on refresh it works..why?
if (loading || error) return <span></span>;
updateCurrentUser({ ...loggedUser, dbUser: data.user });
}
return <span></span>;
}
const mapDispatchToProps = dispatch => {
return {
updateCurrentUser: bindActionCreators(updateCurrentUser, dispatch)
}
}
export default connect(
null,
mapDispatchToProps
)(setUser);