in my Next.js
app, I have a react hook that fetches the currently authenticated user, and sets it to a piece of global state. I want to run this hook once on page load, but I want it to be exposed to every component in the app
import { useState, useEffect } from 'react';
import { useQuery } from '@apollo/client';
import { GET_AUTHED_USER } from '../utils/queries';
import { useAppContext } from '../context';
export const getCurrentUser = () => {
const [isCompleted, setIsCompleted] = useState(false)
const [state, setState] = useAppContext()
const { data: authedUserData } = useQuery(GET_AUTHED_USER, {
onCompleted: () => setIsCompleted(true)
});
useEffect(() => {
Router.push('/home')
if (isCompleted) {
setState({
currentUser: authedUserData?.getAuthedUser,
isAuthed: true,
});
}
}, [isCompleted]);
return [state, setState];
_APP.js
import '../styles/reset.css'
import { AppWrapper } from '../context'
import { getCurrentUser } from '../hooks';
function MyApp({ Component, pageProps }) {
const [state] = getCurrentUser()
console.log(state) // TypeError: Invalid attempt to destructure non-iterable instance.
return (
<AppWrapper>
<Component {...pageProps} />
</AppWrapper>
)
}
export default MyApp
the hook does work in pages/index.js
but that means I can only run it if the /
endpoint is hit.
<AppWrapper/>
is where all the values get originally defined
import { createContext, useContext, useState } from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { getCookie } from '../utils/functions';
const AppContext = createContext();
export function AppWrapper({ children }) {
const URI = 'http://localhost:5000/graphql';
const [state, setState] = useState({
currentUser: null,
isAuthed: false,
});
const httpLink = createHttpLink({
uri: URI,
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = getCookie('JWT');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? token : '',
}
}
});
const client = new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink)
});
return (
<AppContext.Provider value={[state, setState]}>
<ApolloProvider client={client}>
{children}
</ApolloProvider>
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
CodePudding user response:
Interesting question, you want to load that portion of code only once per each browser hit?
Then the location is right. NextJs
make sure when you have a unique browser hit, it runs _app.js
, but only once, after that it'll goes into a single page application mode.
After the above fact, actually whether a piece of code is run only once or twice or multiple time is mostly driven by how many times it detects the "change".
useEffect(() => {
// run
}, [condition])
If the condition changes, it'll run again. However if the condition does not change, but the whole piece is re-mount, it'll run again. You have to consider both fact here.
In short, if you have to run it per route change, make the condition === route.name
. A piece of advice, try work with the single page application first, then work with the unique feature nextJS
, because otherwise it'll be really difficult to figure out the answer.