Custom Google Sign In Button for React Frameworks

Share
Custom Google Sign In Button for React Frameworks
Photo by Firmbee.com / Unsplash

Creating a custom Google button for your web app can be a nice way to have a uniform login screen. This post will explain how to setup a custom button when using a frontend framework such as React / Next / Nuxt.

The easiest way to setup a custom button is to render the Google button in a hidden div, and then simply click it when the user clicks your own custom button.

Typing & Script Setup

Firstly lets setup types for our Google wrapper. Install the types library:

npm install -D @types/google.accounts

Once installed we can setup the window object like so:

declare global {
  interface Window {
    google?: typeof google;
  }
}

The actual Google client script can be added depending on your framework, in Next.JS the following snippet is required:

<Script src="https://accounts.google.com/gsi/client" async defer strategy="afterInteractive" onLoad={handleOnLoad} />

Handling the Library Load

Once the library loads we will setup the button and hide it from the user, the following Next.JS code demonstrates the setup, adjust as required for your framework of choice.

  const ref = useRef<{ click: () => void; }>(null!);

  const handleCredentialResponse = useCallback(async (googleResponse: { clientId: string, client_id: string, credential: string, select_by: string }) => {
    // handle response here
  }, [])

  const createFakeGoogleWrapper = () => {
    const googleLoginWrapper = document.createElement("div");
    googleLoginWrapper.style.display = "none";
    googleLoginWrapper.classList.add("custom-google-button");

    // Add the wrapper to body
    document.body.appendChild(googleLoginWrapper);

    // Use GSI javascript api to render the button inside our wrapper
    window.google.accounts.id.renderButton(googleLoginWrapper, {
      type: "icon",
      width: "200",
    });

    // Get the button inside the wrapper
    const googleLoginWrapperButton = googleLoginWrapper.querySelector("div[role=button]");

    // Return a function that will click the button inside the wrapper
    return {
      click: () => {
        (googleLoginWrapperButton as HTMLButtonElement).click();
      },
    };
  };

  const handleOnLoad = () => {
    if (window.google) {
      window.google.accounts.id.initialize({
        client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
        callback: handleCredentialResponse,
        auto_select: false,
        ux_mode: "popup",
      });
      ref.current = createFakeGoogleWrapper();
    } else {
      console.error("Google API not loaded");
    }
  };

  const handleLoadEffect = useEffectEvent(() => {
    if (window.google) {
      handleOnLoad();
    }
  });

  useEffect(() => {
    // If a client side redirect we can just check if the google api is loaded and initialize the sign in button
    handleLoadEffect();
  }, []);

All we need to trigger this is then a Button like so:

<button onClick={() => ref.current.click()}>
  {loading ? 'Loading...' : 'Sign In'
</button>

A complete GitHub Gist can be found here.