Executing Login
Having the UI of the Login page built in our client, we'll investigate how we can make the query for Google Sign In's authentication URL when a user attempts to sign in.
With the UI of our <Login />
component mostly prepared, we'll now handle how we can make the authUrl
query to direct the user to the Google consent form. We'll then look to handle the response when the consent form redirects a signed-in user back to our client application.
Viewer state object#
Just like how we have the concept of a Viewer
in our server, we'll create the same Viewer
concept in our client (i.e. the person viewing the app). This Viewer
object on the client will determine whether the user has been logged in, and the id
and avatar
of this user is available, as well whether the user has connected to their Stripe account.
We'll create the Viewer
interface type in a types.ts
file within the src/lib/
folder which is to be the file we'll keep type definitions that is to be accessed in multiple parts of the client app.
client/
// ...
src/
lib/
// ...
types.ts
// ...
// ...
The Viewer
interface we'll create will have the same properties as we've seen before - id
, token
, avatar
, hasWallet
, and didRequest
.
export interface Viewer {
id: string | null;
token: string | null;
avatar: string | null;
hasWallet: boolean | null;
didRequest: boolean;
}
For most of the Viewer
fields, null
means the fields requested is not available. In our logIn
mutation, we're requesting all the fields within the Viewer
object type and if they're not available, they will come back as null
values.
In the main starting point of our app, the src/index.tsx
file, we'll import the Viewer
interface and we'll use the useState
Hook to create a viewer
state object that the child components of <App />
will be able to access and use. We'll initialize this viewer
state object with null
values for all the fields except for the didRequest
field which will be given a false
boolean value.
In the useState
Hook, we'll also destruct a setViewer()
function which will be used to update the state viewer
object.
import React, { useState } from "react";
// ...
import { Viewer } from "./lib/types";
const initialViewer: Viewer = {
id: null,
token: null,
avatar: null,
hasWallet: null,
didRequest: false
};
const App = () => {
const [viewer, setViewer] = useState<Viewer>(initialViewer);
};
We'll want to have the setViewer()
function be available in the <Login />
component so the client viewer
object can be updated after the logIn
mutation runs. To have the setViewer()
function be available in the Login
component we can take advantage of React Router's render props pattern and pass in the setViewer()
function.
<Route exact path="/login" render={props => <Login {props} setViewer={setViewer} />} />
React Router has now introduced Hooks! Be sure to check out the React Router Hooks video in Module 15 of the course to highlight how the above can be done with Hooks.
<Login />
#
The <Login />
component now expects a setViewer
prop with which we should check for. In the <Login />
component file, we'll create a Props
interface and state that setViewer
is a prop it will receive. The setViewer
prop is to be a function that receives a viewer
object with which it uses to update the viewer
state property and won't return anything (i.e. void
). We have the interface for what represents a viewer
in our client so we'll import this Viewer
interface and assign it as the type of the viewer
argument within the setViewer
function prop type. We'll then destruct the setViewer
prop from the props argument of the <Login />
component function.
import React from "react";
import { Card, Layout, Typography } from "antd";
import { Viewer } from "../../lib/types";
interface Props {
setViewer: (viewer: Viewer) => void;
}
export const Login = ({ setViewer }: Props) => {
// ...
};
Manual query of authUrl#
We'll continue to work on the <Login />
component. The first thing we'll look to tackle is how we can make the query for the authentication URL from Google Sign-in/OAuth and direct our user to this URL when they attempt to sign-in.
We've created the authUrl
field in our GraphQL API that will provide this authentication URL. We know that the useQuery
Hook from React Apollo runs a query upon component mount. That isn't what we want to do here. In this context, we'll rather have the authUrl
query fired on the click event of the Sign in with Google Button.
To manually fire a query upon an event other than component rendering, React Apollo gives us two options to do so:
Use the
useLazyQuery
HookRun the
query()
function from theclient
object obtained from theuseApolloClient
Hook.
The
useQuery
anduseLazyQuery
Hooks leverage and use theclient
object that can be accessed directly from theuseApolloClient
Hook.
We'll import and use the useApolloClient
Hook. At the top of the <Login />
component, we'll run useApolloClient()
to get the client
object.
// ...
import { useApolloClient } from "@apollo/react-hooks";
// ...
export const Login = ({ setViewer }: Props) => {
const client = useApolloClient();
// ...
};
With this client
object, we have access to a query()
function that will allow us to run the authUrl
query manually. With that said, we'll create a handleAuthorize()
component function that will fire when the user clicks the Sign in with Google button in our <Login>
component template. We'll attach this function as a click listener to the button.
export const Login = ({ setViewer }: Props) => {
const client = useApolloClient();
const handleAuthorize = async () => {};
return (
<Content className="log-in">
{/* ... */}
<button className="log-in-card__google-button" onClick={handleAuthorize}>
{/* ... */}
</button>
{/* ... */}
</Content>
);
};
In the handleAuthorize()
function, we'll want to use the client
object from our useApolloClient
Hook to request the authUrl
query. First, we'll need to import the authUrl
query document from the src/lib/graphql/
folder.
import { AUTH_URL } from "../../lib/graphql/queries";
We'll also import the corresponding type definitions for the authUrl
query data.
import { AuthUrl as AuthUrlData } from "../../lib/graphql/queries/AuthUrl/__generated__/AuthUrl";
In the handleAuthorize()
function, we'll have a try...catch
statement. The try
statement will use the client
object to make our query. The query()
function from client
will appear similar to how the useQuery
Hook behaves. It accepts a type variable for the data (and variables) of the query and an options argument where we can pass in the query document. It returns a result of options where in this case, we're only interested in returning the data
from the query.
const handleAuthorize = async () => {
try {
const { data } = await client.query<AuthUrlData>({
query: AUTH_URL
});
} catch {}
};
With our client application running, if we were to head over the login page and click the Sign in with Google button, we'll notice the API call being made in our browsers developer console.

We'll need to have our app be redirected to the url that's returned from the authUrl
query. To simply redirect our app to the new URL, we'll use the window.location.href
property and set it to the data.authUrl
value.
const handleAuthorize = async () => {
try {
const { data } = await client.query<AuthUrlData>({
query: AUTH_URL
});
window.location.href = data.authUrl;
} catch {}
};
Now, when we click the Sign In With Google Button, we'll be taken to the Google Consent page!

The consent page we see here refers to the project we've set up in our Google cloud developer console with which we've given the name of TinyHouse. If we were to provide a valid email and password for a Google account and sign-in, we'll be redirected back to the /login
route of our app.

Why are we being redirected to /login
? This is because we've stated in the Google Developer Console for our TinyHouse project, the redirect URI of the OAuth client credentials we've generated (and are using) is going to be http://localhost:3000/login
, which is the login page for our development environment.
When we're redirected to the /login
page, Google returns a code
as part of the URL query parameter. The redirected URL will look something like this.
http://localhost:3000/login?code={...}&scope={...}&authuser=0&session_state={...}&prompt=consent#
At this point, we've finished Step 1 of our Google OAuth flow. Our React client must now pass this authorization code
to our Node server where our server will use the code
to communicate with Google's People API and determine if this is an existing user logging to our app or a new user.
logIn
#
We've set up the logIn
mutation in our GraphQL API that when triggered will accept the code
returned from Google and essentially "log in" our user. To run this mutation, we'll use the useMutation
Hook React Apollo provides.
In our <Login />
component, we'll first import the LOG_IN
mutation document.
import { LOG_IN } from "../../lib/graphql/mutations";
We'll import the autogenerated type definitions for the data to be returned from the logIn
mutation and the variables it accepts.
import {
LogIn as LogInData,
LogInVariables
} from "../../lib/graphql/mutations/LogIn/__generated__/LogIn";
Lastly, we'll also import the useMutation
Hook from React Apollo.
import { useApolloClient, useMutation } from "@apollo/react-hooks";
At the top of our <Login />
component, we'll use the useMutation
Hook to destruct the logIn
mutation request as well the results we're interested in. We'll be interested in the data
, loading
, and error
properties with which we'll rename to logInData
, logInLoading
, and logInError
.
export const Login = ({ setViewer }: Props) => {
const client = useApolloClient();
const [
logIn,
{ data: logInData, loading: logInLoading, error: logInError }
] = useMutation<LogInData, LogInVariables>(LOG_IN);
};
We'll want to run the logIn
mutation request in a certain condition. We want to run the request the moment our <LogIn />
component is being rendered and the code
is available as a query parameter. To run an effect in a certain condition like this, we can use React's useEffect
Hook.
We'll import the useEffect
Hook and declare it near the top of our component function.
import React, { useEffect } from "react";
export const Login = ({ setViewer }: Props) => {
const client = useApolloClient();
const [
logIn,
{ data: logInData, loading: logInLoading, error: logInError }
] = useMutation<LogInData, LogInVariables>(LOG_IN);
useEffect(() => {}, []);
};
We'll place an empty dependencies list since we don't want the effect we'll create to ever run after the component has been mounted. We'll access the code
from the URL query parameter with the URL() constructor and the capability to use the searchParams.get()
function. This will allow us to access the value of a certain parameter in our URL. In this case, we're interested in accessing the value of the code
parameter.
useEffect(() => {
const code = new URL(window.location.href).searchParams.get("code");
}, []);
This page is a preview of TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two