Redirect To Previous Page in ReactJS - reactjs

I have a problem on redirect to the previous page since i do a check isLoggedIn. The problem right now is after check isLoggedIn it redirect to the default route. How do i maintain the page where I'm into?
What i did right now is using the referer but it is undefined. Pls help me find another way.
Pls check my code below:
Login.js
const Form = (props) => {
const classes = useStyles();
const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
const referer = props.referer;
const history = useHistory();
console.log(history);
const { values, touched, errors, isSubmitting, handleChange, handleBlur, handleSubmit } = props;
if (isLoggedIn) {
return <Redirect to={referer} />;
}
return (
<div className={classes.root}>
<Grid container direction="row" justify="center">
<Grid item lg={4} md={5} xs={10}>
<Card>
<form onSubmit={handleSubmit}>
<CardHeader
title="LOGIN"
classes={{
title: classes.cardHeader,
}}
className={classes.cardHeader}
/>
<CardContent className={classes.textFieldSection}>
<TextField
fullWidth
label="Username"
name="username"
type="text"
variant="outlined"
value={values.username}
onChange={handleChange}
onBlur={handleBlur}
helperText={touched.username ? errors.username : ''}
error={touched.username && Boolean(errors.username)}
InputProps={{
endAdornment: (
<InputAdornment>
<AccountCircle />
</InputAdornment>
),
}}
/>
<TextField
fullWidth
label="Password"
name="password"
style={{ marginTop: '1rem' }}
type="password"
variant="outlined"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
helperText={touched.password ? errors.password : ''}
error={touched.password && Boolean(errors.password)}
InputProps={{
endAdornment: (
<InputAdornment>
<LockIcon />
</InputAdornment>
),
}}
/>
</CardContent>
<CardActions className={classes.loginButtonSection}>
<Button
type="submit"
color="primary"
variant="contained"
className={classes.loginButton}
disabled={isSubmitting}
>
Log In
</Button>
</CardActions>
</form>
</Card>
</Grid>
</Grid>
</div>
);
};
let yup = require('yup');
export const Login = (props) => {
const dispatch = useDispatch();
const MyFormWithFormik = withFormik({
mapPropsToValues: ({ username, password }) => {
return {
username: username || '',
password: password || '',
};
},
validationSchema: yup.object().shape({
username: yup.string().required('Enter your username'),
password: yup.string().required('Enter your password'),
}),
handleSubmit: (values, { setSubmitting }) => {
dispatch(login(values.username, values.password));
setSubmitting(false);
},
})(Form);
return <MyFormWithFormik />;
};
Form.propTypes = {
className: PropTypes.string,
};
export default Login;
PrivateRoute.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import axios from 'axios';
import { useSelector } from 'react-redux';
function PrivateRoute({ component: Component, ...rest }) {
const authTokens = useSelector((state) => state.auth.access_token);
function checkTokenExpiry() {
if (authTokens) {
axios.interceptors.request.use(
function (config) {
const token = `Bearer ${authTokens}`;
config.headers.Authorization = token;
console.log(config);
return config;
},
(error) => {
console.log(error);
Promise.reject(error);
}
);
}
}
if (authTokens != null) {
checkTokenExpiry();
}
return (
<Route
{...rest}
render={(props) =>
authTokens ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { referer: props.location } }} />
)
}
/>
);
}
export default PrivateRoute;
PrivateAdminOnlyRoute
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import axios from 'axios';
import { useSelector } from 'react-redux';
function PrivateAdminOnlyRoute({ component: Component, ...rest }) {
const authTokens = useSelector((state) => state.auth.access_token);
const isAdmin = useSelector((state) => state.auth.is_admin);
function checkTokenExpiry() {
if (authTokens) {
axios.interceptors.request.use(
function (config) {
const token = `Bearer ${authTokens}`;
config.headers.Authorization = token;
console.log(config);
return config;
},
(error) => {
console.log(error);
Promise.reject(error);
}
);
}
}
if (authTokens != null) {
checkTokenExpiry();
}
return (
<Route
{...rest}
render={(props) =>
authTokens && isAdmin === true ? (
<Component {...props} />
) : authTokens && (isAdmin === false || undefined || null) ? (
<Redirect to={{ pathname: '/pending', state: { referer: props.location } }} />
) : (
<Redirect to={{ pathname: '/login', state: { referer: props.location } }} />
)
}
/>
);
}
export default PrivateAdminOnlyRoute;
Routes.js
import React, { useState } from 'react';
import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import Login from './pages/Login/Login';
import Signup from './pages/Signup/Signup';
import Common from './pages/Common';
function Routes() {
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<PrivateRoute path="/" component={Common} />
</Switch>
</Router>
);
}
export default Routes;

Using state inside Redirect isn't assigan state to props but to location
import { useLocation } from 'react-router-dom';
const location = useLocation();
const referer = location.state && location.state.referer
another thing about your code (that not connect directly to your issue) is
(isAdmin === false || undefined || null)
isAdmin compared just to false, and in case isAdmin isn't false it is not being compared to undefined, but undefined stand by itself (with falsy behavior...), and so goes for null
hope it's helpful :)

Related

react firebase private route

need help making private route with react redux and firebase
the issue is:
when i click on sign in the user is sign it correctly and the state is updated on redux store but the user is not redirected to dashboard but typing "/dashboard" URL works after clicking the button signing in also trying to go "/dashboard" when the user state user null works
App.js
import React, { useEffect } from "react";
import { connect, Provider } from "react-redux";
import store from "./store";
import { setUser, clearUser } from "./actions/userActions";
import {
BrowserRouter as Router,
Route,
Switch,
withRouter,
} from "react-router-dom";
import Login from "./components/auth/Login";
import Register from "./components/auth/Register";
import Home from "./components/pages/Home";
import Dashboard from "./components/pages/Dashboard";
import firebase from "./firebase";
import PrivateRoute from "./components/PrivateRoute";
function App({ setUser, clearUser, currentUser, history, isLoading }) {
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user);
} else {
clearUser();
}
});
return () => {
unsubscribe();
clearUser();
};
}, [clearUser, history, setUser]);
return (
<Switch>
<Route path="/" component={Home} exact />
<PrivateRoute
path="/dashboard"
component={Dashboard}
currentUser={currentUser}
isLoading={isLoading}
/>
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
</Switch>
);
}
const mapStateToProps = (state) => ({
isLoading: state.user.isLoading,
currentUser: state.user.currentUser,
});
const AppWithRouter = withRouter(
connect(mapStateToProps, { setUser, clearUser })(App)
);
const AppWithAuth = () => (
<Provider store={store}>
<Router>
<AppWithRouter />
</Router>
</Provider>
);
export default AppWithAuth;
PrivateRoute.js
import React from "react";
import { Redirect, Route } from "react-router-dom";
const PrivateRoute = ({
component: Component,
currentUser,
isLoading,
...rest
}) => {
return isLoading ? (
<h1>Spinner</h1>
) : (
<Route
{...rest}
render={(props) =>
currentUser ? <Component {...props} /> : <Redirect to="login" />
}
/>
);
};
export default PrivateRoute;
Login.js
import React, { useState } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
//firebase
import firebase from "../../firebase";
import {
Grid,
Box,
TextField,
CssBaseline,
Avatar,
Container,
Typography,
LinearProgress,
Button,
makeStyles,
} from "#material-ui/core";
const Login = (props) => {
const [form, setForm] = useState({
email: "",
password: "",
});
const [errors, setErrors] = useState([]);
const [loading, setLoading] = useState(false);
const handleChange = (event) =>
setForm({ ...form, [event.target.name]: event.target.value });
const handleSubmit = (e) => {
e.preventDefault();
if (!form.email || !form.password) {
return setErrors(["Fill in all fields"]);
}
setErrors([]);
setLoading(true);
firebase
.auth()
.signInWithEmailAndPassword(form.email, form.password)
.then((signedInUser) => {
setLoading(false);
props.history.push("dashboard");
})
.catch((err) => {
setLoading(false);
setErrors([err.message]);
});
};
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center",
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}></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"
label="Email Address"
name="email"
autoComplete="email"
onChange={handleChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
onChange={handleChange}
/>
<Box color="red">
{errors.length > 0 && errors.map((err, i) => <span>{err}</span>)}
</Box>
<Grid justify="center"></Grid>
{loading ? <LinearProgress size={14} /> : ""}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Sign in
</Button>
<Grid container>
<Grid item>
<Link to="/register" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={8}></Box>
</Container>
);
};
const mapStateToProps = (state) => ({
isLoading: state.user.isLoading,
currentUser: state.user.currentUser,
});
export default connect(mapStateToProps)(Login);

Why is react-router-dom <Redirect> not working?

Just trying to simply redirect once authenticated. figured i would convert my login screen to class component because i had a condition to redirect that was causing an endless loop. however once i converted my login screen, the redirect wasnt working at all. Im using react-router-dom v5.2...i read someone say that redirect isnt a part of v5+ and it changed to 'Navigate'...but it definitely was working at one point. and plus when trying to use 'Navigate' it says its not an exported package.
here the login screen:
class LoginScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
password2: '',
role: '',
email: ''
}
}
componentDidUpdate() {
if (this.props.isAuthenticated) {
console.log("Logged in")
return (<Redirect to={'/dashboard'} />)
}
}
render() {
const { email, role, password, password2, username } = this.state
return (
<$.Container>
<$.Wrapper>
<$.Logo />
<$.Form>
<$.Input
type="email"
value={email}
placeholder="Email"
onChange={e => this.setState({ email: e.target.value })}
/>
<$.Input
type="username"
value={username}
placeholder="Username"
onChange={e => this.setState({ username: e.target.value })}
/>
<$.Input
type="password"
value={password}
placeholder="Password"
onChange={e => this.setState({ password: e.target.value })}
/>
<$.Input
type="password"
value={password2}
placeholder="Password Confirm"
onChange={e => this.setState({ password2: e.target.value })}
/>
<$.Input
type="role"
value={role}
placeholder="Role"
onChange={e => this.setState({ role: e.target.value })}
/>
</$.Form>
<$.Button onClick={() => this.props.login(email, password)}>Sign In</$.Button>
<$.Button onClick={() => this.props.register({ email, password, password2, role, username })}>Register</$.Button>
{this.props.message != null ?
this.props.error ?
<$.Error>{this.props.message}</$.Error>
: <$.Info>{this.props.message}</$.Info>
: null}
</$.Wrapper>
</$.Container >
)
}
}
const mapStateToProps = ({ auth }) => {
const { isAuthenticated, message, error } = auth
return { isAuthenticated, message, error }
}
const mapDispatchToProps = { login, register }
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen)
the condtion passes, as the log statement is output. but the redirect isnt redirecting.
heres app.js render:
<Provider store={store}>
<Router>
<div className="App">
<Switch>
{routes.map((route, index) => (
route.private ?
<PrivateRoute exact={route.exact} path={route.path} component={route.main} key={index} />
:
<Route exact={route.exact} path={route.path} component={route.main} key={index} />))}
</Switch>
</div>
</Router>
</Provider>
page redirected to:
import React from 'react'
function DashboardScreen(props) {
return (
<div>
<h1>dashboard screen</h1>
</div>
)
}
export default DashboardScreen
privateRoute component:
import React from 'react'
import { connect } from 'react-redux'
import { Route, Redirect } from 'react-router-dom'
import Sidebar from '../components/navigation/Sidebar'
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={(props) => props.isAuthenticated ? (
<div style={{ display: 'grid', gridTemplateColumns: '200px auto' }}>
<Sidebar role={'super-admin'} />
<Component role={'test-role'} {...props} />
</div>
) : (
<Redirect to={{ pathname: '/', state: { referrer: props.location } }} />
)}
/>
)
}
const mapStateToProps = ({ auth }) => {
const { isAuthenticated } = auth
return { isAuthenticated }
}
export default connect(mapStateToProps)(PrivateRoute)
how can i get this redirect working?
You need to put the Redirect in JSX that is going to be rendered. componentDidUpdate is not going to render that returned value. So instead, remove componentDidUpdate and change the top of your render to:
render() {
const { isAuthenticated } = this.props;
const { email, role, password, password2, username } = this.state;
if (isAuthenticated) {
return (<Redirect to={'/dashboard'} />);
}
return (
...

Add redux actions to login form?

I transitioned from a previous React app to a new template. Issue is i am quite confused about how redux is setup and how i can implement authentication.
LoginForm
// validation functions
const required = value => (value === null ? 'Required' : undefined);
const email = value =>
value && !/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? 'Invalid email' : undefined;
const LinkBtn = React.forwardRef(function LinkBtn(props, ref) {
// eslint-disable-line
return <NavLink to={props.to} {...props} innerRef={ref} />; // eslint-disable-line
});
// eslint-disable-next-line
class LoginForm extends React.Component {
// state = {
// showPassword: false,
// };
constructor() {
super();
this.state = {
email: '',
password: '',
errors: {},
showPassword: false,
};
}
handleClickShowPassword = () => {
const { showPassword } = this.state;
this.setState({ showPassword: !showPassword });
};
handleMouseDownPassword = event => {
event.preventDefault();
};
onSubmit = e => {
e.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password,
};
loginUser(userData);
};
render() {
console.log(this.props);
const { classes, handleSubmit, pristine, submitting, deco } = this.props;
const { showPassword } = this.state;
return (
<Fragment>
<Hidden mdUp>
<NavLink to="/" className={classNames(classes.brand, classes.outer)}>
<img src={logo} alt={brand.name} />
{brand.name}
</NavLink>
</Hidden>
<Paper className={classNames(classes.paperWrap, deco && classes.petal)}>
<Hidden smDown>
<div className={classes.topBar}>
<NavLink to="/" className={classes.brand}>
<img src={logo} alt={brand.name} />
{brand.name}
</NavLink>
<Button
size="small"
className={classes.buttonLink}
component={LinkBtn}
to="/register"
>
<Icon className={classes.icon}>arrow_forward</Icon>
Create new account
</Button>
</div>
</Hidden>
<Typography variant="h4" className={classes.title} gutterBottom>
Sign In
</Typography>
<Typography variant="caption" className={classes.subtitle} gutterBottom align="center">
Lorem ipsum dolor sit amet
</Typography>
<section className={classes.socmedLogin}>
<div className={classes.btnArea}>
<Button variant="outlined" size="small" className={classes.redBtn} type="button">
<AllInclusive className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 1
</Button>
<Button variant="outlined" size="small" className={classes.blueBtn} type="button">
<Brightness5 className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 2
</Button>
<Button variant="outlined" size="small" className={classes.cyanBtn} type="button">
<People className={classNames(classes.leftIcon, classes.iconSmall)} />
Socmed 3
</Button>
</div>
<ContentDivider content="Or sign in with email" />
</section>
<section className={classes.formWrap}>
<form onSubmit={handleSubmit}>
<div>
<FormControl className={classes.formControl}>
<Field
name="email"
component={TextFieldRedux}
placeholder="Your Email"
label="Your Email"
required
validate={[required, email]}
className={classes.field}
/>
</FormControl>
</div>
<div>
<FormControl className={classes.formControl}>
<Field
name="password"
component={TextFieldRedux}
type={showPassword ? 'text' : 'password'}
label="Your Password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
onMouseDown={this.handleMouseDownPassword}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
required
validate={required}
className={classes.field}
/>
</FormControl>
</div>
<div className={classes.optArea}>
<FormControlLabel
className={classes.label}
control={<Field name="checkbox" component={CheckboxRedux} />}
label="Remember"
/>
<Button
size="small"
component={LinkBtn}
to="/reset-password"
className={classes.buttonLink}
>
Forgot Password
</Button>
</div>
<div className={classes.btnArea}>
<Button variant="contained" color="primary" size="large" type="submit">
Continue
<ArrowForward
className={classNames(classes.rightIcon, classes.iconSmall)}
disabled={submitting || pristine}
/>
</Button>
</div>
</form>
</section>
</Paper>
</Fragment>
);
}
}
const mapDispatchToProps = dispatch => ({
init: bindActionCreators(loginUser, dispatch),
loginUser:
});
LoginForm.propTypes = {
classes: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
submitting: PropTypes.bool.isRequired,
loginUser: PropTypes.func.isRequired,
deco: PropTypes.bool.isRequired,
};
const LoginFormReduxed = reduxForm({
form: 'immutableExample',
enableReinitialize: true,
})(LoginForm);
const reducerLogin = 'login';
const reducerUi = 'ui';
const FormInit = connect(
state => ({
force: state,
initialValues: state.getIn([reducerLogin, 'usersLogin']),
deco: state.getIn([reducerUi, 'decoration']),
}),
mapDispatchToProps,
)(LoginFormReduxed);
export default withStyles(styles)(FormInit);
login.js
import React from 'react';
import { Helmet } from 'react-helmet';
import brand from 'dan-api/dummy/brand';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import { LoginForm } from 'dan-components';
import styles from 'dan-components/Forms/user-jss';
class Login extends React.Component {
state = {
valueForm: [],
};
submitForm(values) {
const { valueForm } = this.state;
setTimeout(() => {
this.setState({ valueForm: values });
console.log(`You submitted:\n\n${valueForm}`);
window.location.href = '/app';
}, 500); // simulate server latency
}
render() {
const title = brand.name + ' - Login';
const description = brand.desc;
const { classes } = this.props;
return (
<div className={classes.root}>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
</Helmet>
<div className={classes.container}>
<div className={classes.userFormWrap}>
<LoginForm onSubmit={values => this.submitForm(values)} />
</div>
</div>
</div>
);
}
}
Login.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Login);
My authactions i am trying to add.
import axios from 'axios';
import jwt_decode from 'jwt-decode';
import setAuthToken from '../../utils/setAuthToken';
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post('/api/total/users/login', userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem('jwtToken', JSON.stringify(token));
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
}),
);
};
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
// User loading
export const setUserLoading = () => {
return {
type: USER_LOADING,
};
};
// Log user out
export const logoutUser = history => dispatch => {
// Remove token from local storage
localStorage.removeItem('jwtTokenTeams');
// Remove auth header for future requests
setAuthToken(false);
// Set current user to empty object {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
history.push('/dashboard');
};
and the authreducer
import { Map, fromJS } from 'immutable';
import { INIT } from '../constants/reduxFormConstants';
import { SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
const isEmpty = require('is-empty');
const initialState = {
usersLogin: Map({
isAuthenticated: false,
user: {},
loading: false,
remember: false,
}),
};
const initialImmutableState = fromJS(initialState);
export default function reducer(state = initialImmutableState, action = {}) {
switch (action.type) {
case INIT:
return state;
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload,
};
case USER_LOADING:
return {
...state,
loading: true,
};
default:
return state;
}
}
I'm having a really hard time understaning how i can make this work together.
adding app.js
/**
* app.js
*
* This is the entry file for the application, only setup and boilerplate
* code.
*/
// Needed for redux-saga es6 generator support
import '#babel/polyfill';
// Import all the third party stuff
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router/immutable';
import FontFaceObserver from 'fontfaceobserver';
import history from 'utils/history';
import 'sanitize.css/sanitize.css';
// Import root app
import App from 'containers/App';
import './styles/layout/base.scss';
// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';
// Load the favicon and the .htaccess file
import '!file-loader?name=[name].[ext]!../public/favicons/favicon.ico'; // eslint-disable-line
import 'file-loader?name=.htaccess!./.htaccess'; // eslint-disable-line
import configureStore from './redux/configureStore';
// Import i18n messages
import { translationMessages } from './i18n';
// Observe loading of Open Sans (to remove open sans, remove the <link> tag in
// the index.html file and this observer)
const openSansObserver = new FontFaceObserver('Open Sans', {});
// When Open Sans is loaded, add a font-family using Open Sans to the body
openSansObserver.load().then(() => {
document.body.classList.add('fontLoaded');
});
// Create redux store with history
const initialState = {};
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE,
);
};
if (module.hot) {
// Hot reloadable React components and translation json files
// modules.hot.accept does not accept dynamic dependencies,
// have to be constants at compile-time
module.hot.accept(['./i18n', 'containers/App'], () => {
ReactDOM.unmountComponentAtNode(MOUNT_NODE);
render(translationMessages);
});
}
// Chunked polyfill for browsers without Intl support
if (!window.Intl) {
new Promise(resolve => {
resolve(import('intl'));
})
.then(() =>
Promise.all([import('intl/locale-data/jsonp/en.js'), import('intl/locale-data/jsonp/de.js')]),
) // eslint-disable-line prettier/prettier
.then(() => render(translationMessages))
.catch(err => {
throw err;
});
} else {
render(translationMessages);
}
// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
if (process.env.NODE_ENV === 'production') {
require('offline-plugin/runtime').install(); // eslint-disable-line global-require
}
the app component
import React from 'react';
import jwt_decode from 'jwt-decode';
import { Switch, Route } from 'react-router-dom';
import NotFound from 'containers/Pages/Standalone/NotFoundDedicated';
import store from '../../redux/configureStore';
import { setCurrentUser, logoutUser } from '../../redux/actions/authActions';
import setAuthToken from '../../utils/setAuthToken';
import Auth from './Auth';
import Application from './Application';
import ThemeWrapper, { AppContext } from './ThemeWrapper';
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
class App extends React.Component {
render() {
console.log(this.props);
return (
<ThemeWrapper>
<AppContext.Consumer>
{changeMode => (
<Switch>
<Route path="/" exact component={Auth} />
<Route
path="/app"
render={props => <Application {...props} changeMode={changeMode} />}
/>
<Route component={Auth} />
<Route component={NotFound} />
</Switch>
)}
</AppContext.Consumer>
</ThemeWrapper>
);
}
}
export default App;
the auth component
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Outer from '../Templates/Outer';
import {
Login,
LoginV2,
LoginV3,
Register,
RegisterV2,
RegisterV3,
ResetPassword,
LockScreen,
ComingSoon,
Maintenance,
NotFound,
} from '../pageListAsync';
class Auth extends React.Component {
render() {
return (
<Outer>
<Switch>
<Route path="/login" component={Login} />
{/* <Route path="/login-v2" component={LoginV2} />
<Route path="/login-v3" component={LoginV3} />
<Route path="/register" component={Register} />
<Route path="/register-v2" component={RegisterV2} />
<Route path="/register-v3" component={RegisterV3} /> */}
<Route path="/reset-password" component={ResetPassword} />
<Route path="/lock-screen" component={LockScreen} />
{/* <Route path="/maintenance" component={Maintenance} />
<Route path="/coming-soon" component={ComingSoon} /> */}
<Route component={NotFound} />
</Switch>
</Outer>
);
}
}
export default Auth;
To maintain authentication using PLain redux is not quite possible because when ever you reload, the store get refreshed . However, redux has a functionality called Persisted store
Persisted Store store the data in memory and will not be refreshed with page reload or anything like that.
You can check this Link
Update with out persisted store:
In that case, Get the IsLoggedin state from store.
In App component
const App = () => {
const isLoggedIn = localStorage.getItem("jwtToken") !== null ? true: false
return (
<Router>
<Switch>
<PrivateRoute isLoggedIn={isLoggedIn} path="/dashboard">
<Dashboard />
</PrivateRoute>
<Route path="/login">
<Login authToken={authToken} />
</Route>
</Switch>
</Router>
);
and then in private route component:
const PrivateRoute = (props) => {
const { children, IsLoggedin, ...rest } = props;
return (
<Route
{...rest}
render={({ location }) =>
IsLoggedin ? (
children
) : (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
)
}
/>
);
};

Unauthorized page restriction wants a refresh

I'm getting started with ReactJS and I wanted to restrict the sign-in page for not authorized users only and I want to redirect those users to the page they were before after they log in (that part is not working as well).
The problem is that when I log in and then go back to /sign-in page, it says that I'm still not authorized, even tho I can literally see the access token in local storage (look at the picture below). However, if I refresh the page, NotAuthorizedRoute works and it redirects me back to home page.
How can I fix this in React? How can I also redirect back to the previously opened page and not / only?
In other words, const isLoggedIn = localStorage.getItem('access_token') !== null; is being activated only after a page refresh. In angular, I used to fix such things by #Input/#Output and EventEmitter but I'm new to React and I don't know how to deal with it yet.
Image:
Login.js
import React, { Fragment, useState } from 'react';
import { useHistory } from 'react-router-dom';
import UserService from '../../Services/UserService';
import {
makeStyles,
Typography,
Container,
TextField,
FormControlLabel,
Checkbox,
Button,
Grid,
CircularProgress
} from '#material-ui/core';
const useStyles = makeStyles(theme => ({
content: {
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(8, 0, 6)
},
form: {
marginTop: theme.spacing(1)
},
submit: {
margin: theme.spacing(3, 0, 2)
}
}));
const Login = () => {
const classes = useStyles();
const history = useHistory();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
const user = {
username,
password,
};
setLoading(true);
UserService.loginUser(user)
.then(res => {
localStorage.setItem('access_token', res.token);
setLoading(false);
history.push('/');
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}
return (
<Fragment>
<div className={classes.content}>
<Container maxWidth="sm">
<Typography component="h1" variant="h2" align="center" color="textPrimary" gutterBottom>
Sign In
</Typography>
<Typography variant="h5" align="center" color="textSecondary" paragraph>
If you are not registered, you should sign up.
</Typography>
</Container>
</div>
<Container maxWidth="md">
<Grid container justify="center" spacing={3}>
<Grid item xs={6}>
{!UserService.isLoggedIn ?
<form className={classes.form} noValidate onSubmit={handleSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
type="text"
label="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
error={error !== ''}
helperText={error}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
type="password"
label="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={loading}
>
Sign In
</Button>
</form>
:
<Grid container justify="center">
Logged in.
</Grid>
}
{loading &&
<Grid container justify="center">
<CircularProgress className={classes.spinner} />
</Grid>
}
</Grid>
</Grid>
</Container>
</Fragment>
);
}
export default Login;
App.js
import React, { Fragment } from 'react';
import './App.css';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import NotAuthorizedRoute from './Helpers/NotAuthorizedRoute';
import Navbar from './Components/Navbar/Navbar';
import Home from './Components/Home/Home';
import User from './Components/User/User';
import Login from './Components/Login/Login';
function App() {
return (
<Fragment>
<Router>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/users" component={User} />
<NotAuthorizedRoute path="/sign-in" component={Login} />
<Redirect from="*" to="/" />
</Switch>
</Router>
</Fragment>
);
}
export default App;
Helpers/NotAuthorizedRoute.js
import React from 'react';
import UserService from '../Services/UserService';
import { Redirect, Route } from 'react-router-dom';
const NotAuthorizedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props =>
!UserService.isLoggedIn ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
)
}
export default NotAuthorizedRoute;
Service/UserService.js
const isLoggedIn = localStorage.getItem('access_token') !== null;
const loginUser = async (user) => {
const { username, password } = user;
if (username === 'qwe' && password === '123') {
return { token: 'access_token' };
} else {
throw new Error('Wrong username or password');
}
}
export default { isLoggedIn, loginUser };
EDIT: If I put !localStorage.getItem('access_token') instead of !UserService.isLoggedIn in NotAuthorizedRoute, it works. Why?
In React, the component only re-renders when props or state changes. In your NotAuthorizedRoute you're directly using isLoggedIn param from file. Instead, you should pass isLoggedIn as prop from parent component. So you can re-write NotAuthorizedRoute.js as:
import React from 'react';
import UserService from '../Services/UserService';
import { Redirect, Route } from 'react-router-dom';
const NotAuthorizedRoute = ({ component: Component, isLoggedIn=false, ...rest }) => {
return (
<Route
{...rest}
render={props =>
!UserService.isLoggedIn ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
)
}
export default NotAuthorizedRoute;
and call this component as
<NotAuthorizedRoute ... isLoggedIn={UserService.isLoggedIn} />

Render nav after login

I am working on the react project. Currently, I am struggling to render the nav again after sign in to show the links that are only visible for logged in users. The following code works fine, and also the redirect goes to "/".
The main issue is that the links for logged-in users only visible after a reload of the page.
Nav.js
import React, {Component} from "react";
import {Nav, Navbar} from 'react-bootstrap';
import { Auth } from 'aws-amplify'
import SignIn from "./Authentication/SignIn";
import SignUp from "./Authentication/SignUp";
import SignOut from "./Authentication/SignOut";
import {
BrowserRouter as Router,
withRouter,
Redirect,
Switch,
Route,
Link
} from "react-router-dom";
import Share from "./Share";
import Subscribe from "./Subscribe";
import Home from "./Home"
class PrivateRoute extends React.Component {
constructor (props) {
super(props);
this.state = {
loaded: false,
isAuthenticated: false
}
}
componentDidMount() {
this.authenticate()
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log('user: ', user))
.catch(() => {
if (this.state.isAuthenticated) this.setState({ isAuthenticated: false })
})
});
}
componentWillUnmount() {
this.unlisten()
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true});
})
.catch(() => this.props.history.push('/signup'))
}
render() {
const { component: Component, ...rest } = this.props
const { loaded , isAuthenticated} = this.state
if (!loaded) return null
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/signup",
}}
/>
)
}}
/>
)
}
}
PrivateRoute = withRouter(PrivateRoute)
class Routes extends React.Component {
constructor (props) {
super(props);
this.state = {
showItemInMenu: false
}
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then(() => { this.setState({showItemInMenu: true })})
.catch(() => { this.setState({showItemInMenu: false})});
}
render() {
const showItemInMenu = this.state.showItemInMenu
return (
<Router>
<Navbar bg="dark" variant="dark">
<Navbar.Brand as={Link} to="/">Todo</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
<Nav.Link as={Link} to="/">Home</Nav.Link>
{showItemInMenu && <Nav.Link as={Link} to="/share" >Share</Nav.Link>}
{showItemInMenu && <Nav.Link as={Link} to="/subscribe" >Subscribe</Nav.Link> }
{showItemInMenu && <Nav.Link as={Link} to="/signout" >Logout</Nav.Link> }
<Nav.Link as={Link} to="/signup" >Registration</Nav.Link>
<Nav.Link as={Link} to="/signin" >Login</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/signup' component={SignUp}/>
<Route path='/signin' component={SignIn}/>
<PrivateRoute path='/share' component={Share}/>
<PrivateRoute path='/subscribe' component={Subscribe}/>
<PrivateRoute path='/signout' component={SignOut}/>
</Switch>
</Router>
)
}
}
export default Routes
Signin.js
import React, { Component } from "react";
import { Form, Row, Col,Button, Alert,Container } from 'react-bootstrap';
import { Auth } from "aws-amplify";
import styled from 'styled-components';
import { Redirect } from 'react-router-dom'
class SignIn extends Component {
constructor (props) {
super(props);
this.state = {
username: '',
password: '',
message: '',
redirect: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSignIn = this.handleSignIn.bind(this);
}
handleChange (event) {
this.setState({ [event.target.name]: event.target.value });
}
handleSignIn (event){
event.preventDefault();
const username = this.state.username;
const password = this.state.password;
// You can pass an object which has the username, password and validationData which is sent to a PreAuthentication Lambda trigger
Auth.signIn({
username,
password
})
.then(user => console.log(user))
.then(() => { this.setState({ redirect: true })
})
.catch(err =>
this.setState({message: err.message})
);
};
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect
to={{
pathname: "/",
state: { fromlogin: true }
}}
/>
}
}
render () {
const Title = styled.div`
margin-top: 10px;
margin-bottom: 15px;
`;
return (
<Container>
<Row>
<Col md={6}>
<Title>
<h3>Login Form</h3>
</Title>
<Form onSubmit={this.handleSignIn}>
<Form.Group>
<Form.Control type="text" name="username" placeholder="Username" onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Control type="password" name="password" placeholder="Password" onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Button
variant="primary"
type="submit"
value="SIGN IN"
>LOGIN</Button>
</Form.Group>
</Form>
{this.state.message ?
<Alert variant="danger">{this.state.message}</Alert>
: <></>}
</Col>
</Row>
{this.renderRedirect()}
</Container>
);
}
}
export default SignIn
I was able to solve this using React JS context API and local storage for storing the required state globally so that it does not revert on refresh.
Scenario: A top navbar containing site title is to visible every time regardless of user being signed in or not. The username and a logout button to appear in the top navbar only for logged in users. A side menu bar visible only for signed in users.
Issue: The username only appears after a reload to the main page just like the links in the above question.
Solution:
Create a file to store user context
UserContext.js
let reducer = (user, newUser) => {
if (newUser === null) {
localStorage.removeItem("user");
return newUser;
}
return { ...user, ...newUser };
};
const userState = {
name: "",
email: "",
};
const localState = JSON.parse(localStorage.getItem("user"));
const AuthContext = createContext();
const AuthProvider = (props) =>{
const [user, setUser] = useReducer(reducer, localState || userState);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(user));
}, [user]);
return(
<AuthContext.Provider value={{ user, setUser }}>
{props.children}
</AuthContext.Provider>
)
}
export { AuthContext, AuthProvider };
Now in your App.js file wrap this AuthProvider over the components where user context is intended to be accessed.
App.js
function App() {
return (
<AuthProvider>
<Router>
<NavBar />
<Switch>
<Route path="/signin" exact component={SignIn} />
<Route path="/signup" component={SignUp} />
<ProtectedRoute path="/home" component={Home} />
</Switch>
</Router>
</AuthProvider>
);
}
Now consume the context in the components that were wrapped with the context like the Home and Navbar component.
NavBar.js
export default function MiniDrawer() {
const {user, setUser} = useContext(AuthContext)
//set user context and local/global storage respective value to null on signout
const handleSignOut = async () =>
{
try
{
await Auth.signOut()
localStorage.removeItem("user");
setUser(null)
history.push("/signin")
}catch(err)
{
console.log(err.message)
alert('Could not sign out due to: ', err.message)
}
}
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open,
})}
>
<Toolbar>
{ user && user.name &&
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, {
[classes.hide]: open,
})}
>
<MenuIcon />
</IconButton>
}
<Typography variant="h6" noWrap>
The Video Site
</Typography>
{ user && user.name &&
<div className={classes.userArea}>
<Typography >
Welcome, {user && user.name}
</Typography>
<ListItem button onClick={handleSignOut}>
<ListItemIcon>{<ExitToAppIcon /> }</ListItemIcon>
<ListItemText primary="Log out" />
</ListItem>
</div>
}
</Toolbar>
</AppBar>
{ user && user.name &&
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
}),
}}
>
<div className={classes.toolbar}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</div>
<Divider />
<List>
{
SideBarData.map( (item) => {
return(
<Link to={item.path}>
<Tooltip title={item.title} aria-label="add">
<ListItem button key={item.title} >
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.title} />
</ListItem>
</Tooltip>
</Link>
)
}
)
}
</List>
<Divider />
</Drawer>
}
<main className={classes.content}>
<div className={classes.toolbar} />
{/* <MediaUpload /> */}
</main>
</div>
);
}
Now, you have implemented context with local storage in order to maintain state even after refreshing.
Guidance source

Resources