Home > Net >  Exposing every component in Next.js app to custom hook on pageLoad
Exposing every component in Next.js app to custom hook on pageLoad

Time:04-23

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.

  • Related