In this part of the tutorial we will take the client ID we got in part 1 and hook it up to a Login and Logout component that will use the react-google-login library. Here's the packages you need
"dependencies": {
"@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
"@material-ui/styles": "^4.11.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-google-login": "^5.2.2",
"react-redux": "^7.2.2",
"react-scripts": "4.0.1",
"redux": "^4.0.5"
}
(Already assuming you have your CRA + redux setup)
The documentation for react-google-login is very helpful and concise. The only thing I feel is missing are instructions to get your Google OAuth client id which I covered in the 1st article.
Now we could use the built in components shipped with the library and supply them with specific props to look something like this...
<GoogleLogin
clientId="zzzzz"
buttonText="Login"
onSuccess={responseGoogle} //Some function
onFailure={responseGoogle}
/>
//same for GoogleLogout component
This is serviceable but we eventually want to set up a theme for our project and have the styles of all our components switch when we go from light mode -> dark mode. So we will create our own Login and Logout button from Material UI components and use the hooks useGoogleLogin() useGoogleLogout() also in this library to make our buttons.
The props that went into the GoogleLogin component are now passed into this hook function as an object
The useGoogleLogout() hook is pretty much identical in functionality so the most important part is now writing the onSuccess and onFailure functions for both. Let's check the docs to see what those functions should do
The expected object to be returned if the authentication succeeds is something with the tokenId, profileObj, googleId (etc...) properties. If that same request fails an object is sent to onFailure that includes an error property along with some details about the message.
Time to Code
...With that we have everything we need to start coding! Since we will store user information in the redux store let's set up those pieces first
Setting up your redux folders is highly opinionated but this is how I set up my folders
|- reducers
|- types
|- actions
1) Types:
Fairly simple there are only 2 possible states; the user logging in and the user logging out
export const SIGN_IN = 'SIGN_IN';
export const SIGN_OUT = 'SIGN_OUT';
2) Action Creators:
We'll write two functions that return action objects one for Login and one for Logout.
/*
Will be passed in as the onSuccess and onFailure callback
for useGoogleLogin() hook
/*
export const googleOAuthLogin = (response) => {
const action = { type: SIGN_IN };
let payload;
if (typeof response === 'undefined' || response.error) {
//If login fails
payload = null;
} else {
payload = response;
}
action.payload = payload;
return action;
};
For the logout we don't necessarily need to handle any errors (see we didn't even accept an argument here) because all we're really doing is just clearing the user out of the redux store.
/*
Will be passed in as the onSuccess and onFailure callback
for useGoogleLogout() hook
/*
export const googleOAuthLogout = () => {
const action = { type: SIGN_OUT };
return action;
};
3) Reducer
I chose to store this piece of state in the redux store under the "auth" key which has an initial state I defined to be...
const INITIAL_STATE = {
loggedIn: false,
user: null
};
We write the reducer handling both SIGN_IN and SIGN_OUT actions (don't forget to use default parameters!). For SIGN_IN our action.payload is null if it failed, but if it succeeded our Google response object should be in there.
case SIGN_IN:
let loggedIn = action.payload ? true : false;
return loggedIn ?
{...state,loggedIn,user: {
tokenId: action.payload.tokenId,
...action.payload.profileObj
}
}
: { ...state, loggedIn, user: null };
/*
If successful looks state looks like
{loggedIn: true, user: {tokenId: "sometoken" , profileObj: {}}
*/
case SIGN_OUT:
return { ...INITIAL_STATE};
4) Login Component
Without talking too much about the actual CSS used to make the buttons let's quickly examine how to use the built-in styling system for Material UI
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
center: {
display: 'flex',
justifyContent: 'center'
},
button: {
textTransform: 'none',
marginTop: theme.spacing(10),
display: 'flex',
alignItems: 'center',
boxShadow: theme.shadows[3],
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
transition: 'background-color 0.5s',
'&:hover': {
backgroundColor: theme.palette.primary.dark,
transition: 'background-color 0.5s',
cursor: 'pointer'
}
},
}));
We return an object from the makeStyles callback that has key value pairs where the keys are class names you wish to use and the value is always an object that specifies CSS properties. (Keep in mind CSS properties are camelCased)
*What is the theme variable? *
Since we don't have a theme defined yet Material UI supplies a default theme for us that you can see here (its just a giant object)
To use these classes we simply call the function above (we called it useStyles) and we get back our object.
const classes = useStyles();
return(
<p className={classes.center}> Hello! </p>
)
I will be using the hooks provided by react-redux but this is equivalent to use mapStateToProps (replaced by useSelector) and mapDispatchToProps(replaced by useDipatch; we have to manually dispatch it)
...
import googleLogo from '../images/google-logo.png';
import { useDispatch } from 'react-redux';
...
const clientId =
'blahblahblahblah.apps.googleusercontent.com';
function Login() {
const onSuccess = (res) => {
dispatch(googleOAuthLogin(res));
};
const onFailure = (res) => {
dispatch(googleOAuthLogin(res));
};
const { signIn } = useGoogleLogin({
onSuccess,
onFailure,
clientId,
isSignedIn: true
});
return (
<Container component='section' className={classes.center}>
<Button className={classes.button} onClick={signIn}>
<Avatar src={googleLogo} className={classes.avatar} />
<Typography component='p' variant='h6' className={classes.text}>
Sign in with Google
</Typography>
</Button>
</Container>
);
}
To keep the styling consistent we use the same makeStyles object for the Logout component the only difference is the functions passed into useGoogleLogout are called onFailure and onLogoutSuccess.
function Logout() {
const classes = useStyles();
const dispatch = useDispatch();
const onSuccess = (res) => {
dispatch(googleOAuthLogout(res));
};
const onFailure = (res) => {
dispatch(googleOAuthLogout(res));
};
const { signOut } = useGoogleLogout({
onFailure,
onLogoutSuccess: onSuccess,
clientId,
isSignedIn: true
});
return (
<Container component='section' className={classes.center}>
<Button className={classes.button} onClick={signOut}>
<Avatar src={googleLogo} className={classes.avatar} />
<Typography component='p' variant='h6' className={classes.text}>
Sign out of Google
</Typography>
</Button>
</Container>
);
}
Last Step!
Lets go to App.js (or whatever your root component is called) and add conditional rendering for these components. We'll reach into the store with useSelector and check the auth.loggedIn property to determine what button to show.
function App() {
const auth = useSelector((state) => state.auth);
return (
<main>
{auth.loggedIn ? <Logout /> : <Login />}
</main>
</ThemeProvider>
);
}
Your project should look something like this now!
Part 3 will set up the Alert system through redux and the theme system so we can easily switch from light to dark mode.
Top comments (0)