Implementing the Login form is similar to the Register form, however, it involves two steps instead of one. The client must first retrieve the salt from the API, use it to hash the password, and then send a second request to the API to log in. Your implementation may look like this:
import React from 'react';
import bcrypt from 'bcryptjs';
import { validator } from '../../utils';
import Button from '../button/index.jsx';
import Input from '../input/index.jsx';
function retrieveSalt (email) {
const url = new URL('http://%%API_SERVER_HOST%%:%%API_SERVER_PORT%%/salt/');
url.search = new URLSearchParams({ email });
const request = new Request(url, {
method: 'GET',
mode: 'cors'
});
return fetch(request)
.then(response => {
if (response.status === 200) {
return response.text();
} else {
throw new Error('Error retrieving salt');
}
})
}
function login (email, digest) {
// Send the credentials to the server
const payload = { email, digest };
const request = new Request('http://%%API_SERVER_HOST%%:%%API_SERVER_PORT%%/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
mode: 'cors',
body: JSON.stringify(payload)
})
return fetch(request)
.then(response => {
if (response.status === 200) {
return response.text();
} else {
throw new Error('Error logging in');
}
})
}
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
token: null,
email: {
value: "",
valid: null
},
password: {
value: "",
valid: null
}
};
}
handleLogin = (event) => {
event.preventDefault();
event.stopPropagation();
const email = this.state.email.value;
const password = this.state.password.value;
retrieveSalt(email)
.then(salt => bcrypt.hashSync(password, salt))
.then(digest => login(email, digest))
.then(token => this.setState({ token }))
.catch(console.error)
}
handleInputChange = (name, event) => {
const value = event.target.value;
const valid = validator[name](value);
this.setState({
[name]: { value, valid }
});
}
render() {
if(this.state.token) {
return (
<div id="login-success">
<h1>You have logged in successfully!</h1>
<p>Where do you want to go next?</p>
<Link to='/'><Button title="Home"></Button></Link>
<Link to='/profile'><Button title="Profile"></Button></Link>
</div>
)
}
return [
<form onSubmit={this.handleLogin}>
<Input label="Email" type="email" name="email" id="email" value={this.state.email.value} valid={this.state.email.valid} onChange={this.handleInputChange} />
<Input label="Password" type="password" name="password" id="password" value={this.state.password.value} valid={this.state.password.valid} onChange={this.handleInputChange} />
<Button title="Login" id="login-button" disabled={!(this.state.email.valid && this.state.password.valid)}/>
</form>,
<p>Don't have an account? <Link to='/register'>Register</Link></p>
]
}
}
export default LoginForm;
Now that we have the form component ready, let's add it to the router. In React Router versions prior to v4, you can simply add a new <Route> component to <BrowserRouter>:
<BrowserRouter>
<Route exact path="/register" component={RegistrationForm} />,
<Route exact path="/login" component={LoginForm} />
</BrowserRouter>
However, with React Router v4, Router components can only have one child component. Therefore, we must encase the <Route> components inside a container.
The react-router-dom package provides a <Switch> component, which we will use as our container. The <Switch> component will render only the component specified in the first matching <Route>:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import RegistrationForm from './components/registration-form/index.jsx';
import LoginForm from './components/login-form/index.jsx';
ReactDOM.render((
<BrowserRouter>
<Switch>
<Route exact path="/register" component={RegistrationForm} />,
<Route exact path="/login" component={LoginForm} />
</Switch>
</BrowserRouter>
), document.getElementById('renderTarget'));
In the preceding example, if we navigate to /register, the <Switch> component will see that there's a match in the first <Route> component, and will stop looking for any more matches and return <RegistrationForm>.