skip to Main Content

I’m working on an App that needs to load the "https://accounts.google.com/gsi/client" api, I used the Script component of NextJS, when try to navigate using the Link from NextJS, the script doesn’t load again. But if enter the url directly on the browser it fetch and load everything. I don’t know why the Link is not working properly.

The Script is on a ReactContext that manages the load of the Google script.

This is the Context and context provider

import { googleGsiClient } from '@/utils';
import Script from 'next/script';
import React, { createContext, useEffect, useState } from 'react';

export interface IGoogleSignInContext {
  scriptLoaded: boolean;
  scriptError?: string;
}

export const GoogleSignInContext = createContext<IGoogleSignInContext>({
  scriptLoaded: false,
});

export const GoogleSignInProvider: React.FC<{
  children: JSX.Element | JSX.Element[];
}> = ({ children }) => {
  const [state, setState] = useState<IGoogleSignInContext>({
    scriptLoaded: false,
  });

  const handleScriptLoaded = () => {
    console.log('GOOGLE SCRIPT LOADED')
    setState((actual) => ({
      ...actual,
      scriptLoaded: true,
    }));
  };

  const handleScriptError = () => {
    setState((actual) => ({
      ...actual,
      scriptLoaded: false,
      scriptError: 'Failed to load script',
    }));
  };

  useEffect(() => {
    console.log('MOUNTING THE GOOGLE PROVIDER');
  }, []);

  return (
    <>
      <GoogleSignInContext.Provider value={state}>
        <Script
          src={googleGsiClient}
          onLoad={handleScriptLoaded}
          onError={handleScriptError}
        />
        {children}
      </GoogleSignInContext.Provider>
    </>
  );
};

This is my google button Component

import { GoogleSignInContext } from '@/context/GoogleSignIn'
import { GoogleCredentialResponse } from '@/interfaces';
import React, { useContext, useEffect, useRef } from 'react'

export interface GoogleSignInButtonProps {
  onSingIn?: (credentials: GoogleCredentialResponse) => void;
  calcWidth: number | null;
}

export const GoogleSignInButton: React.FC<GoogleSignInButtonProps> = ({
  onSingIn,
  calcWidth
}) => {

  const googleContext = useContext(GoogleSignInContext);
  const buttonRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    
    if(!googleContext.scriptLoaded) return;
    console.log(calcWidth);
    if(!buttonRef) return;
    if(!calcWidth) return;

    window.google?.accounts.id.initialize({
      client_id: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENTID,
      callback(credentialResponse: GoogleCredentialResponse) {
          onSingIn?.(credentialResponse);
      },
    });

    window.google?.accounts.id.renderButton(buttonRef.current!, {
      type: 'standard',
      shape: 'rectangular',
      size: 'large',
      theme: 'outline',
      locale: "en_US",
      width: calcWidth.toString(),
    })

  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [googleContext.scriptLoaded, calcWidth, buttonRef])

  if(!googleContext.scriptLoaded || !calcWidth) return <></>;

  return (
    <div ref={buttonRef} id='google-button' style={{height: 45}}></div>
  )
}

My login and register are different pages. So i can nagivate between those with the Link

Portion of code in Login to navigate to register

          <div className="mt-5 d-flex justify-content-around">
            <small>Don&apos;t have an account?</small>
            <small>
              <Link href={"/auth/register"} prefetch={false}>Create account</Link>
              
              {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
              {/* <a href='/auth/register'>Sign In</a> */}
            </small>
          </div>

Portion of code in Register to navigate to login

              <div className="mt-5 d-flex justify-content-around">
                <small>Already have an account? </small>
                <small>
                  <Link href={'/auth'} prefetch={false}>Sign In</Link>
                  {/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
                  {/* <a href='/auth'>Sign In</a> */}
                </small>
              </div>

How can I use the NextJS Link and make the load of the script working? Thanks.

2

Answers


  1. Chosen as BEST ANSWER

    I've already found a solution, when the page is loaded for the first time, it fetch the script but then the script remain in the document no matter if you do navigation between pages. So in the context that manage the script I added a patch for check if the script is in the document when component is mounted, and do not load the Script component if exist because it causes some strange behaviour. This is my context now

    import { googleGsiClient } from '@/utils';
    import Script from 'next/script';
    import React, { createContext, useEffect, useState } from 'react';
    
    export interface IGoogleSignInContext {
      scriptLoaded: boolean;
      scriptError?: string;
    }
    
    export const GoogleSignInContext = createContext<IGoogleSignInContext>({
      scriptLoaded: false,
    });
    
    export const GoogleSignInProvider: React.FC<{
      children: JSX.Element | JSX.Element[];
    }> = ({ children }) => {
      const [state, setState] = useState<IGoogleSignInContext>({
        scriptLoaded: true,
      });
    
      const handleScriptLoaded = () => {
        if (state.scriptLoaded) return; //already in document
    
        console.log('GOOGLE SCRIPT LOADED');
        setState((actual) => ({
          ...actual,
          scriptLoaded: true,
        }));
      };
    
      const handleScriptError = (e: any) => {
        console.log(e);
        setState((actual) => ({
          ...actual,
          scriptLoaded: false,
          scriptError: 'Failed to load script',
        }));
      };
    
      useEffect(() => {
        console.log('MOUNTING THE GOOGLE PROVIDER');
        const gsiScript = document.getElementById('google-gsi-script');
    
        if (gsiScript) {
          console.log('GSI SCRIPT ALREADY LOADED', gsiScript);
          setState((actual) => ({
            ...actual,
            scriptLoaded: true,
          }));
        } else {
          setState((actual) => ({
            ...actual,
            scriptLoaded: false,
          }));
        }
      }, []);
    
      return (
        <>
          <GoogleSignInContext.Provider value={state}>
            {!state.scriptLoaded ? (
              <Script
                key={'google-gsi-script'}
                id="google-gsi-script"
                src={googleGsiClient}
                onLoad={handleScriptLoaded}
                onError={handleScriptError}
                async
                defer
              />
            ) : (
              <></>
            )}
            {children}
          </GoogleSignInContext.Provider>
        </>
      );
    };
    
    

  2. Well, another way more straightforward is to use the onReady handler of the Script component, is called everytime the component is mounted and the script loaded

    This works, when navigation occurs it calls the onReady and the script is already to use

    import { googleGsiClient } from '@/utils';
    import Script from 'next/script';
    import React, { createContext, useEffect, useState } from 'react';
    
    export interface IGoogleSignInContext {
      scriptLoaded: boolean;
      scriptError?: string;
    }
    
    export const GoogleSignInContext = createContext<IGoogleSignInContext>({
      scriptLoaded: false,
    });
    
    export const GoogleSignInProvider: React.FC<{
      children: JSX.Element | JSX.Element[];
    }> = ({ children }) => {
      const [state, setState] = useState<IGoogleSignInContext>({
        scriptLoaded: false,
      });
    
      const handleScriptLoaded = () => {
    
      };
    
      const handleScriptError = (e: any) => {
        console.log(e);
        setState((actual) => ({
          ...actual,
          scriptLoaded: false,
          scriptError: 'Failed to load script',
        }));
      };
    
      const handleOnReady = () => {
        setState((actual) => ({
          ...actual,
          scriptLoaded: true,
        }));
      };
    
      useEffect(() => {
        console.log('MOUNTING THE GOOGLE PROVIDER 2');
      }, []);
    
      return (
        <>
          <GoogleSignInContext.Provider value={state}>
            <Script
              key={'google-gsi-script'}
              id="google-gsi-script"
              src={googleGsiClient}
              onLoad={handleScriptLoaded}
              onError={handleScriptError}
              onReady={handleOnReady}
              async
              defer
            />
            {children}
          </GoogleSignInContext.Provider>
        </>
      );
    };
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search