This article is suitable for beginners. We will create sign-in and User Profile screens. Moreover, we will incorporate an API from MeCallAPI.com.
API
We will use a free Login API from MeCallAPI.com with the following detail:
-
Method: POST
-
Body (JSON):
{
"username": "karn.yong@mecallapi.com",
"password": "mecallapi"
}
- Response:
{
"status": "ok",
"message": "Logged in",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"fname": "Karn",
"lname": "Yong",
"username": "karn.yong@mecallapi.com",
"email": "karn.yong@mecallapi.com",
"avatar": "https://www.mecallapi.com/users/1.png"
}
}
For more APIs with populated fake data, such as user list and user profile authentication with JWT, these can be found at MeCallAPI.com.
Software Installation
-
Node.js https://nodejs.org
-
Yarn
npm install --global yarn
Create React App
Use create-react-app to create an initial React application.
npx create-react-app react-basic-login
Go inside the folder.
cd react-basic-login
Add more packages
We will add the following packages to our application:
-
Material UI
-
react-router-dom
-
Sweetalert
yarn add @material-ui/core @material-ui/icons react-router-dom sweetalert
Create screens for Sign-in and Profile
We will create 2 screens:
-
Sigin.js for login page with the use of login API
-
Profile.js for displaying user profile (must be logged in before going to this screen)
Create Sigin.js (From line 41 is the use of login API from MeCallAPI.com)
import React, { useState } from "react";
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import swal from "sweetalert";
const useStyles = makeStyles((theme) => ({
root: {
height: "100vh",
},
image: {
backgroundImage: "url(https://source.unsplash.com/random)",
backgroundSize: "cover",
},
paper: {
margin: theme.spacing(8, 4),
display: "flex",
flexDirection: "column",
alignItems: "center",
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: "100%",
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
async function loginUser(credentials) {
return fetch("https://www.mecallapi.com/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
}).then((data) => data.json());
}
export default function Signin() {
const classes = useStyles();
const [username, setUserName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async (e) => {
e.preventDefault();
const response = await loginUser({
username,
password,
});
if ("accessToken" in response) {
swal("Success", response.message, "success", {
buttons: false,
timer: 2000,
}).then((value) => {
localStorage.setItem("accessToken", response["accessToken"]);
localStorage.setItem("user", JSON.stringify(response["user"]));
window.location.href = "/profile";
});
} else {
swal("Failed", response.message, "error");
}
};
return (
<Grid container className={classes.root}>
<CssBaseline />
<Grid item xs={false} md={7} className={classes.image} />
<Grid item xs={12} md={5} component={Paper} elevation={6} square>
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate onSubmit={handleSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
name="email"
label="Email Address"
onChange={(e) => setUserName(e.target.value)}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="password"
name="password"
label="Password"
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
</form>
</div>
</Grid>
</Grid>
);
}
Create Profile.js
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import MenuItem from "@material-ui/core/MenuItem";
import Menu from "@material-ui/core/Menu";
import Avatar from "@material-ui/core/Avatar";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
large: {
width: theme.spacing(20),
height: theme.spacing(20),
},
}));
export default function Profile() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const user = JSON.parse(localStorage.getItem("user"));
const handleMenu = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleLogout = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
window.location.href = "/";
};
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
Profile
</Typography>
<div>
<IconButton onClick={handleMenu} color="inherit">
<Avatar src={user.avatar} />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem onClick={handleLogout}>Logout</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
<Card className={classes.root} variant="outlined">
<CardContent>
<Avatar src={user.avatar} className={classes.large} />
<Typography variant="h5">
Welcome {user.fname} {user.lname}
</Typography>
</CardContent>
</Card>
</div>
);
}
Finally, edit App.js for routing between Sign-in and Profile screens. If there is no access token from calling login API, the user will be able to access only Sign-in screen.
import React from "react";
import "./App.css";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Signin from "./Signin";
import Profile from "./Profile";
function App() {
const token = localStorage.getItem("accessToken");
if (!token) {
return <Signin />;
}
return (
<div className="wrapper">
<BrowserRouter>
<Switch>
<Route path="/profile">
<Profile />
</Route>
<Route path="/">
<Profile />
</Route>
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
Testing
Start React:
yarn start
Testing with Email Address and Password:
-
karn.yong@mecallapi.com / mecallapi
-
ivy.cal@mecallapi.com / mecallapi