14

I am using the Sign In With Google button from Google Identity. I have put the HTML from this button documentation page into a React component. Looks like this:

export default function GoogleLoginButton() {

  return (
    <>
      <div
        id="g_id_onload"
        data-client_id="XXXXXX"
        data-auto_prompt="false"
      ></div>
      <div
        className="g_id_signin"
        data-type="standard"
        data-size="large"
        data-theme="outline"
        data-text="sign_in_with"
        data-shape="rectangular"
        data-logo_alignment="left"
      ></div>
    </>
  );
}

On loading the page the first time the Google sign-in button appears correctly and I can log in. The sign-in button is then replaced by a log-out button. The problem is that when I click the log-out button which should render the Google sign-in button again, it doesn't reappear! Why is that?

I can add that refreshing the page after logging out brings back the Google button.

4 Answers4

11

As Stian says, the google script injects the google button once the app renders. The problem is that if you re-render the component where your button is located it will disappear because the google script was already executed, and won't be executed in future re-renders (it won't inject the google button again).

A different solution is to call window.google.accounts.id.renderButton inside an useEffect, that useEffect should be placed inside the component that is re-rendered. This will re-inject the google button each time the useEffect is called (comoponent is re-rendered).

The first argument the renderButton method receives should be a ref to a div, the google script will inject the sign-in button inside that div.

NOTE: Remember to first initialize the google script calling google.accounts.id.initialize

Here's the example:

const divRef = useRef(null);

useEffect(() => {
  if (divRef.current) {
    window.google.accounts.id.initialize({
      client_id: <YOUR_CLIENT_ID_GOES_HERE>,
      callback: (res, error) => {
        // This is the function that will be executed once the authentication with google is finished
      },
    });
    window.google.accounts.id.renderButton(divRef.current, {
      theme: 'filled_blue',
      size: 'medium',
      type: 'standard',
      text: 'continue_with',
    });
  }
}, [divRef.current]);

return (
  {/* Other stuff in your component... */}

  {/* Google sign in button -> */} <div ref={divRef} />
);
Alex Rendón
  • 559
  • 4
  • 6
  • This just yields errors in a TS project, and even extending window it errors too. I've ended up using react-google-login library and the button no longer disappears – user.io Apr 19 '22 at 15:22
  • Worked for me, although I used only `window.google.accounts.id.renderButton` part, because the rest is called by `
    `
    – Marat Zaynutdinoff Jun 30 '22 at 13:11
  • 1
    Good answer. I ended up using this answer over just simply changing the display to none because this answer worked in all of my cases. I had some issues trying to use the next/script component and just simply used a js script tag to load the library like ``. I loaded that script in the page level. And then made a component like the answer above to reuse in several spots. – Nick Oct 19 '22 at 21:32
5

The reason it doesn't work is because the accompanying client library doesn't run again on later renders.

On page load the client library runs and injects an iframe where the HTML is. On later renders one can see in the DOM that this doesn't happen; the HTML is present but no iframe.

One solution is to never remove the HTML from DOM. Instead, apply the style display: none to the sign-in button when in need of hiding it.

3

I found another solution to this problem by creating a useEffect hook that inserts the Google button <script> tag into the DOM every time the component renders. This prevents the button from disappearing when navigating away from the page and back again. The full React component then looks as follows:

const GoogleButton = () => {
  useEffect(() => {
    const script = document.createElement('script');

    script.src = 'https://accounts.google.com/gsi/client';
    script.async = true;
    script.defer = true;

    document.body.appendChild(script);

    return () => {
      document.body.removeChild(script);
    };
  }, []);

  return (
    <>
      <div
        id='g_id_onload'
        data-client_id='your-client-id'
      />
      <div
        className='g_id_signin'
        data-type='standard'
        data-size='large'
        data-theme='outline'
        data-text='sign_in_with'
        data-shape='rectangular'
        data-logo_alignment='center'
      />
    </>
  );
};
rj3005
  • 111
  • 1
  • 7
2

For TypeScript, I have modified Alex's answer a bit.

First don't forget to add the <script src="https://accounts.google.com/gsi/client" async defer></script> to the the index.html page found in the react public folder.

Create a google_sso.ts file and use this code:

import { useRef, useEffect } from 'react';
declare const google: any;

const GoogleSSO = () => {

    const g_sso = useRef(null);

    useEffect(() => {
        if (g_sso.current) {
            google.accounts.id.initialize({
                client_id: "xxxxxxxx-koik0niqorls18sc92nburjfgbe2p056.apps.googleusercontent.com",
                callback: (res: any, error: any) => {
                    // This is the function that will be executed once the authentication with google is finished
                },
            });
            google.accounts.id.renderButton(g_sso.current, {
                theme: 'outline',
                size: 'large',
                type: 'standard',
                text: 'signin_with',
                shape: 'rectangular',
                logo_alignment: 'left',
                width: '220',
            });
        }
    }, [g_sso.current]);


    return (<div ref={g_sso} />);
}


export default GoogleSSO 

Then wherever you need the button, use this:

import  GoogleSSO from "../common/google_sso";

<GoogleSSO />
realPro
  • 1,713
  • 3
  • 22
  • 34