44

I'm trying to use the google sign in in a react application. While using the sign in button as is outside the application itself works great, when using it within a custom SignIn component I can't get it to work as expected. When the user signs in, the button itself should execute a data-onsuccess method. The problem is that the execution never reaches that method even though the sign in works.

I'm probably missing some react gotcha but I can't really find it. Any help? Find below the html that loads everything and the component itself.

<head>
    <meta name="google-signin-scope" content="profile email">
    <meta name="google-signin-client_id" content="1234-real-client-id.apps.googleusercontent.com">
    <script src="https://apis.google.com/js/platform.js" async defer></script>
</head>
<body>
    <!-- Here is where everything gets displayed -->
    <div id="app"></div>

    <!-- The file with the js code -->
    <script src="/js/main.js"></script>
</body>


var SignIn = React.createClass({
    onSignIn : function (google_user) {
        // I want this method to be executed
    },

    render : function() {
        return (
            <div className="g-signin2" data-onsuccess={this.onSignIn} data-theme="dark" />
        );
    }
});

Notice that I didn't paste here irrelevant code ;)

Michael Yaworski
  • 13,410
  • 19
  • 69
  • 97
ThisIsErico
  • 1,885
  • 2
  • 19
  • 24
  • This video explains Google, facebook and linked In login with reactjs -https://youtu.be/9MhLHkf7Ifs – Prem Aug 04 '17 at 14:15

6 Answers6

36

Try adding the onSuccess callback when you initialize the component in componentDidMount().

componentDidMount: function() {
  gapi.signin2.render('g-signin2', {
    'scope': 'https://www.googleapis.com/auth/plus.login',
    'width': 200,
    'height': 50,
    'longtitle': true,
    'theme': 'dark',
    'onsuccess': this. onSignIn
  });  
},
...

Sources: https://developers.google.com/identity/sign-in/web/build-button, https://github.com/meta-meta/webapp-template/blob/6d3e9c86f5c274656ef799263c84a228bfbe1725/app/Application/signIn.jsx.

BradByte
  • 11,015
  • 2
  • 37
  • 41
5

I needed to use this in a functional component so thought I would share how I adapted it, I needed to use the useEffectHook which can be used in place of componentDidMount

  useEffect(() => {
    window.gapi.signin2.render('g-signin2', {
      'scope': 'https://www.googleapis.com/auth/plus.login',
      'width': 200,
      'height': 50,
      'longtitle': true,
      'theme': 'dark',
      'onsuccess': onSignIn
    })
  })
Max Carroll
  • 4,441
  • 2
  • 31
  • 31
  • 3
    I tried your code, but `onsuccess` is not getting triggered. Any other setup that is required? – Ejaz Sep 03 '19 at 10:25
  • not sure what that could be, I am passing my onsuccess method in as a prop from the parent node, have you tried the obvious stuff by debugging and ensuring your onsuccess callback is defined a the point of execution, perhaps stick a breakpoint on the window.gapi on line 2 and ensure that null isnt being passed in there, otherwise, this fuction should be called after they have completed the sign in process (e.g. after they select and account, enter their password and aprove any scopes that need approval) - But to answer your question, all else that is required is to define `onSignIn` – Max Carroll Sep 23 '19 at 17:26
  • @Ejaz Maybe check my answer: https://stackoverflow.com/a/59040581/996314 – Rokit Nov 25 '19 at 21:47
  • 2
    Resolved mine by using the **GoogleAuth.isSignedIn.listen()** based on https://developers.google.com/identity/sign-in/web/reference. It goes like this `window.gapi.load('auth2', () => { const auth2 = window.gapi.auth2.init({ config goes here }); auth2.isSignedIn.listen(() => { your code here }) })` . If you want to get the user profile you can use **GoogleUser.getBasicProfile()**. Hope this helps. – Yor Feb 05 '20 at 15:11
3

Now you can use this React Google Login NPM package that encapsulates all the setup work. You can just pass options to the component only.

import React from 'react';
import ReactDOM from 'react-dom';
import GoogleLogin from 'react-google-login';
// or
import { GoogleLogin } from 'react-google-login';
 
 
const responseGoogle = (response) => {
  console.log(response);
}
 
ReactDOM.render(
  <GoogleLogin
    clientId="658977310896-knrl3gka66fldh83dao2rhgbblmd4un9.apps.googleusercontent.com"
    buttonText="Login"
    onSuccess={responseGoogle}
    onFailure={responseGoogle}
    cookiePolicy={'single_host_origin'}
  />,
  document.getElementById('googleButton')
);

The code is from the package's documentation.

AV Paul
  • 2,299
  • 1
  • 9
  • 11
  • react-google-login worked great! I had to make use of both, react state and localstorage, to get the refresh after login. i.e: `onSuccess={(response) => { this.setState({ userLoggedIn: true, }, () => { localStorage.setItem('userLoggedIn', true); }); ` – Miguel Reyes Aug 16 '19 at 22:52
2

Just make sure you add google's platform.js script tag to your React app's index.html:

<script src="https://apis.google.com/js/platform.js" async defer></script>

Get your Client ID from:

https://console.developers.google.com/apis/credentials


EDIT Updated example with a real-world implementation I use in deployed production applications

And here's a sample SignInWithGoogle function component implementation in Typescript so you can use as inspiration:

// window.ts
export {};

declare global {
  interface Window {
    gapi: any;
  }
}


// index.tsx
import './window';

import React, {
  useEffect,
  useState,
} from 'react';

const SignInWithGoogle = () => {
  const [loaded, setLoaded] = useState<boolean>(false);
  const [signedIn, setSignedIn] = useState<boolean>(false);

  const onSignIn = (googleUser: {[k: string]: any}) => {
    if (!signedIn) {
      setSignedIn(true);

      const token = googleUser.getAuthResponse().id_token;

      // do something with the token (like send it to your api)...
    }
  };

  const onFail = ({
    error,
  }: {
    error: string;
  }) => {
    // do something
  };

  useEffect(() => {
    const { gapi } = window;

    if (!loaded) {
      gapi.load('auth2', () => {
        gapi.auth2.init().then((auth: any) => {
          auth.signOut().then(() => {
            gapi.signin2.render('google-signin-button', {
              width: 232,
              height: 40,
              longtitle: true,
              onsuccess: onSignIn,
              onfailure: onFail,
            });
          });
        });

        setLoaded(true);
      });
    }
  });

  return (
    <div
      id="google-signin-button"
      className="google-signin-button"
    />
  );
};

export default SignInWithGoogle;
JeanLescure
  • 1,029
  • 9
  • 19
2

Might be alittle late but I want to share my go to implementation. As in my case I want to fully customize the button, and also want to have some sort of loader. I reimplemented this using hooks

/* istanbul ignore file */
import { useState, useEffect, useCallback } from 'react';
import { APP_IDS } from 'utils/constants';

const initgAuth = () =>
  // eslint-disable-next-line no-unused-vars
  new Promise((resolve, _reject) => {
    // eslint-disable-next-line no-undef
    gapi.load('auth2', () => {
      // eslint-disable-next-line no-undef
      const auth = gapi.auth2.init({
        client_id: APP_IDS.Google,
      });
      resolve(auth);
    });
  });

const initializeGoogle = () =>
  // eslint-disable-next-line no-unused-vars
  new Promise((resolve, _reject) => {
    if (typeof gapi !== 'undefined') {
      window.googleAsyncInit().then(auth => {
        resolve(auth);
      });
    } else {
      // eslint-disable-next-line func-names
      window.googleAsyncInit = async function() {
        const auth = await initgAuth();
        resolve(auth);
        return auth;
      };

      // eslint-disable-next-line func-names
      (function(d, s, id) {
        let js;
        const gjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) {
          return;
        }
        // eslint-disable-next-line prefer-const
        js = d.createElement(s);
        js.id = id;
        js.src = 'https://apis.google.com/js/platform.js';
        js.onload = window.googleAsyncInit;
        gjs.parentNode.insertBefore(js, gjs);
      })(document, 'script', 'google_api');
    }
  });

const useGoogle = () => {
  const [google, setGoogle] = useState([]);
  const [isReady, setReady] = useState(false);

  const initGoogle = useCallback(async () => {
    const auth = await initializeGoogle();
    if (auth !== 'undefined') {
      setGoogle(auth);
      setReady(true);
    }
  });

  useEffect(() => {
    initGoogle();
  }, [initGoogle]);

  return [google, isReady];
};

So I can juse use this at any component like this

  const [GoogleInstance, isGoogleReady] = useGoogle();

   GoogleInstance.signIn().then(response => {
      const authResponse = response.getAuthResponse(true);
      doGoogleLogin(authResponse);
    });

Its quite convenient because I am able to make sure that the SDK is loaded before user can interact with the button itself. Also usable if you have different components/pages that needs to have google login

keysl
  • 2,127
  • 1
  • 12
  • 16
1

None of the other answers worked for me. The thing that finally worked was using an id instead of a class for the identifier on the div.

<div id="g-signin2" />

instead of

<div className="g-signin2" />

In addition to using window.gapi.signin2.render as other answers suggest.

Alex L
  • 135
  • 1
  • 8