I'm creating a React Router app and I'm learning authentication. Here are some parts:
Login component (login.jsx)
- authenticates user's credential on the back end
- sets the authentication token in local storage
- return's the user's data
- sets state with the data
- passes state to Admin component
Admin component (admin.jsx)
- protected by a private route (see privateroute.jsx)
- basically a container component
- passes user's data to and renders other components that display/edit the data
Authentication Button on the nav bar (authbutton.jsx)
- checks if user is logged in and renders "login" or "logout" button
- if logged in, also renders "my posts" button that routes to Admin
All works well from Login to Admin. My problem is that when I click away from the Admin page (like the home page) and then click on "my posts" button, it reroutes to Admin and knows I'm logged in, but the user's data is no longer available. Before, coming from the login component, the user's data was in this.props.location.state.me
.
I'm stuck because I'm trying to route to Admin from two different components and I've never done that before. Furthermore, I feel like there's a solution in the authentication setup that I'm missing.
Other ideas:
Should I conditionally set state in Admin when the user's data is passed?
Should I store the data in local storage in the browser like I'm doing with the authentication token?
I tried fetching data, setting state in Admin with componentDidMount
but it didn't re-render so I read to use componentWillReceiveProps
but that's being deprecated and replaced with getDerivedStateFromProps
. Could not figure that out.
login.jsx
import React, { Component, Fragment } from 'react';
import * as userService from '../../services/user';
import { Redirect } from 'react-router-dom';
import IndeterminateProgress from '../utilities/indeterminateprogress';
import Nav from '../home/nav';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
redirectToReferrer: false,
email: '',
password: '',
feedbackMessage: '',
checkingLogin: true,
me: ''
};
}
componentDidMount() {
userService.checkLogin()
.then((loggedIn) => {
if (loggedIn) {
this.setState({ redirectToReferrer: true, checkingLogin: false });
} else {
this.setState({ checkingLogin: false });
}
});
}
login(e) {
e.preventDefault();
userService.login(this.state.email, this.state.password)
.then((meData) => {
this.setState({ redirectToReferrer: true, me: meData })
})
.catch((err) => {
if (err.message) {
this.setState({ feedbackMessage: err.message });
}
});
}
handleEmailChange(value) {
this.setState({ email: value });
}
handlePasswordChange(value) {
this.setState({ password: value });
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/admin', state: { ...this.state } } };
const { redirectToReferrer, checkingLogin } = this.state;
if (checkingLogin) {
return <IndeterminateProgress message="Checking Login Status..." />;
}
if (redirectToReferrer) {
return (
<Redirect to={from} />
);
}
return (
<Fragment>
<Nav />
<h2 className="heading center">Login to continue</h2>
<form className="center" onSubmit={(e) => this.login(e)}>
<div className="form-group">
<input
placeholder="Email"
id="email"
className="col-3"
type="email"
onChange={(e) => this.handleEmailChange(e.target.value)}
required
/>
</div>
<div className="form-group">
<input
placeholder="Password"
id="password"
className="col-3"
type="password"
onChange={(e) => this.handlePasswordChange(e.target.value)}
required
/>
</div>
{this.state.feedbackMessage ? (
<p>{this.state.feedbackMessage}</p>
) : null}
<input type="submit" value="Login" className="btn btn-info btn-sm" />
</form>
</Fragment>
);
}
}
export { Login };
admin.jsx
import React, { Component } from 'react';
import Nav from '../home/nav';
import AdminBlogContainer from './adminblogcontainer'
import { BrowserRouter as Router, Link } from 'react-router-dom';
const Admin = (props) => {
return (
<div className="flexcol center">
<Nav />
<h1 className="heading">Your Blog Posts</h1>
<AdminBlogContainer {...props.location.state.me} />
<Link to={{
pathname: '/write',
state: { ...props.location.state.me }
}}
className="btn btn-outline-secondary mt-4"
>Create a New Blog Post</Link>
</div>
)
}
export { Admin };
privateroute.jsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isLoggedIn } from '../../services/user';
const PrivateRoute = (props) => {
const Component = props.component;
const propsToPass = Object.assign({}, props);
delete propsToPass.component;
return (
<Route {...propsToPass} render={props => (
isLoggedIn() ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)
)} />
);
};
export { PrivateRoute }
authbutton.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import { isLoggedIn } from '../../services/user';
const AuthButton = (props) => {
if (isLoggedIn()) {
return (
<div>
<Link className="btn btn-info m-1" to="/logout">Logout</Link>
<Link className='btn btn-info m-1' to={{
pathname: '/admin',
// state: { ...this.state }
}}
>My Posts</Link>
</div>
);
} else {
return (
<div>
<Link className="btn btn-info m-1" to="/login">Login</Link>
<Link className="btn btn-info m-1" to="/register">Register</Link>
</div>
)
}
};
export { AuthButton };