How to add official Google sign-in to your React application
The hard part of this is, the official Google sign-in is using a script
tag in your HTML and initialize it using the traditional in-browser vanilla JavaScript, which is not happening in React. And make it a little bit harder to make the two works together.
1. You add the script to the <head>
of HTML template
1 | <meta |
If you use create-react-app
, it locates in /public/index.html
.
If you don’t have that your-client-ID
, open this page, and click that CONFIGURE A PROJECT button.
2. You write a React component
The idea is, you just render a plain div
, then after componentDidMount
, use the Google API to re-render this div
as a real follow-google-design button, even better, you can bind your React component method to its onsuccess
or onfailure
callback.
1 | const GOOGLE_BUTTON_ID = "google-sign-in-button"; |
More info about the render
API and the others can be found here.
Now you can control the sign-in behavior from your React code.
3. What about you rely on the Google method when starting your app?
If you ever need to consume API under gapi.auth2
, this is where things become hard. Because you have to initialize it. But the official example is using vanilla Javascript. Even worse, loading the <script>
and mounting of your React application are totally happening in parallel, so when you consume the API, the google script may not even be loaded.
I first did something simple, I removed the async
and defer
attribute from the <script>
tag. And add an onload
attribute to an initializing function.
1 | <script> |
It appears working at first, but when you refresh the page, it’s always going to throw an exception if you try to consume gapi.auth2.blahblah
, it seems the <script>
loading happens after mounting of your React app, even though we have removed its async
and defer
attributes to make it blocking rendering.
That means we need to know the lifecycle of the loading
<script>
in your React app in order to get things done
For instance, I want to use its isSignedIn
API for a simple local protected route, if the user is signed-in, let the user access the resource, otherwise, route the user to the login page.
4. Steps to get things done
4.1 First, add <script>
tag
1 | <meta |
Yes, it’s the same as before, async
and defer
will be there, and we will be fine.
4.2 Manage the initializing gapi.auth2
in React and mark its procedure
The idea here is:
- We use
setInterval
to verify whether the<script>
tag has been loaded or not - if loaded, we then
initialize
thegapi.auth2
object - For a better UX, we will save the steps in global store (Redux/MobX), then we can display different UI according to the different status, don’t worry, it will be pretty fast most of the time, should take only less than 2 seconds.
- After initializing, we will
clearInterval
- And when the status is been marked as done, your
React
app can use the Google API without any problems, because all the preparation has been done.
4.3 The code for setInterval
Here in that googleLoadTimer
will continue to check if the window.gapi
is ready if it’s ready, that means <script>
has been loaded. And it will clearInterval
as a teardown as we mentioned before after finishing initializing the gapi.auth2
object.
1 | function initGoogle(func) { |
The authStore
is a mobx store. Feel free to use a redux store here, you just need to dispatch
some actions
to a reducer
which will be used later to indicate the steps, and you only need one property in this reducer, you can refer to the following mobx store code. You just need to swap authStore.setAuthLoadingStatus(LOADING_STATUS.INITIAL);
with something like dispatch(initialAction())
if you are using Redux.
4.4 The code for AuthStore
1 | export class AuthStore { |
LOADING_STATUS
is an object for preventing relying on the string directly.
1 | export const LOADING_STATUS { |
4.5 Nearly done
The googleLoadTimer
will be run when the app starts. And updates the authStore.authLoadingStatus
, in your code. You can now know if the preparation is done like this:
1 | const isReady = authStore.authLoadingStatus === LOADING_STATUS.LOADED; |
If isReady
equals true, that means you can start using it.
I encapsulate the logic into a top-level component, ProtectedRoute
, it’s a simple route for protecting routes from un-google-logged-in user. Which means, before it uses GoogleUser.isSignedIn()
, it will show the loading spinners according to the authStore.authLoadingStatus
, it will only invoke the GoogleUser.isSignedIn()
when the isReady === true
(Otherwise, you will get exception because gapi.auth2
is not ready to use).
5. End
Hope it helps.
Thanks for reading!
Follow me (albertgao) on twitter, if you want to hear more about my interesting ideas.