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
Related
I'm new to react but Im trying to create a web app that is essentially 2 apps in one. By 2 apps in one I mean I have 2 separate layouts, one when authorized and one when not. I'm running into a problem when logging in currently, when I try to redirect on successful log in I get the following error:
Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.
I'm sure this has something to do with how I have my routes set up or how Im redirecting.
Heres all my code. The error is usually pointing to the history.push line in login.js in the handlesubmit function. Note: my code is actually split into multiple js files for each function, I just combined them here so the code would be a bit more compact (I also combined the imports just for this example).
Update: I think I narrowed down my problem to my ProtectedRoute component, but I still dont totally know what the problem is. I think its how Im passing in an array of paths to that component but Im not sure how to fix it.
import React, { useState,useEffect } from "react";
import { NavLink, Route, Switch, useRouteMatch, useHistory, useLocation, useParams } from 'react-router-dom';
import MainLayout from "../layouts/MainLayout";
import AuthLayout from "../layouts/AuthLayout";
import NotFound from "../pages/NotFound";
import Login from "../pages/Login";
import Welcome from "../pages/Welcome";
import Dashboard from "../pages/Dashboard";
import Locations from "../pages/Locations";
import ProtectedRoute from "./ProtectedRoute";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import { LinkContainer } from "react-router-bootstrap";
import { ReactComponent as Logo } from '../images/Logo.svg';
import { useAppContext } from "../libs/contextLib";
import { LinkContainer } from "react-router-bootstrap";
import { useAppContext } from "../libs/contextLib";
import axios from 'axios';
import Form from "react-bootstrap/Form";
import LoaderButton from "../components/LoaderButton";
import LoginImage from '../images/Login-Page-Image.png';
import FloatLabelTextBox from "../components/FloatLabelTextBox.js"
import { API_BASE_URL, ACCESS_TOKEN_NAME } from '../constants/apiConstants.js';
import { onError } from "../libs/errorLib";
export default function MainRoutes() {
return (
<Switch>
<Route path={['/login', '/welcome']}>
<AuthLayout>
<Route path='/login' component={Login} />
<Route path='/welcome' component={Welcome} />
</AuthLayout>
</Route>
<ProtectedRoute exact path={['/', '/locations']}>
<MainLayout>
<Route path='/locations' component={Locations} />
<Route exact path='/' component={Dashboard} />
</MainLayout>
</ProtectedRoute>
{/* Finally, catch all unmatched routes */}
<Route>
<NotFound />
</Route>
</Switch>
);
}
function AuthLayout({children}) {
const { isAuthenticated } = useAppContext();
return (
<>
<div className="AuthLayout container py-3">
<Navbar collapseOnSelect expand="md" className="mb-3 login-nav">
<LinkContainer to="/welcome">
<Navbar.Brand href="/welcome" className="font-weight-bold text-muted">
<Logo />
</Navbar.Brand>
</LinkContainer>
<Navbar.Toggle />
<Navbar.Collapse className="justify-content-end">
<Nav activeKey={window.location.pathname}>
<LinkContainer to="/welcome">
<Nav.Link>Home</Nav.Link>
</LinkContainer>
<LinkContainer to="/login">
<Nav.Link>Login</Nav.Link>
</LinkContainer>
</Nav>
</Navbar.Collapse>
</Navbar>
<div className="Auth-Layout-Body">
{children}
</div>
</div>
</>
);
}
export default AuthLayout;
function MainLayout({ children }) {
const { isAuthenticated } = useAppContext();
const { userHasAuthenticated } = useAppContext();
const history = useHistory();
function handleLogout() {
userHasAuthenticated(false);
console.log("log out");
history.push("/login");
}
return (
<>
<div className="MainLayout container py-3">
<Navbar collapseOnSelect expand="md" className="mb-3 login-nav">
<LinkContainer to="/">
<Navbar.Brand href="/" className="font-weight-bold text-muted">
Location INTEL
</Navbar.Brand>
</LinkContainer>
<Navbar.Toggle />
<Navbar.Collapse className="justify-content-end">
<Nav activeKey={window.location.pathname}>
<LinkContainer to="/">
<Nav.Link>Home</Nav.Link>
</LinkContainer>
<LinkContainer to="/locations">
<Nav.Link>Locations</Nav.Link>
</LinkContainer>
{isAuthenticated ? (
<Nav.Link onClick={handleLogout}>Logout</Nav.Link>
) : (<div></div>)}
</Nav>
</Navbar.Collapse>
</Navbar>
<div className="Main-Layout-Body">
{children}
</div>
</div>
</>
);
}
export default MainLayout;
export default function Login() {
const history = useHistory();
const [state, setState] = useState({
email: "",
password: "",
});
const { userHasAuthenticated } = useAppContext();
const [isLoading, setIsLoading] = useState(false);
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
})
}
function validateForm() {
return state.email.length > 0 && state.password.length > 0;
}
function handleSubmit(event) {
event.preventDefault();
setIsLoading(true);
const payload = {
"email": state.email,
"password": state.password,
}
try {
axios.post('/api/user/login', payload, {
headers: {
useCredentails: true,
'x-api-key': ACCESS_TOKEN_NAME,
"Access-Control-Allow-Origin": "*"
}
})
.then(function (response) {
console.log(response);
//console.log('status code = ' + response.status);
if (response.status === 200) {
console.log("logged in");
userHasAuthenticated(true);
history.push("/");
} else {
console.log("not logged in");
}
})
.catch(function (error) {
console.log(error);
});
} catch (e) {
onError(e);
setIsLoading(false);
}
}
return (
<div className="Login-Container">
<div className="Login-Container-Row">
<div className="Login">
<p className="Login-Header">Login</p>
<div className="Login-Form">
<Form onSubmit={handleSubmit}>
<Form.Group size="lg" controlId="email">
<FloatLabelTextBox
inputLabel="EMAIL"
inputAutoFocus="autofocus"
inputType="email"
inputName="email"
inputPlaceholder="Email"
inputValue={state.email}
handleChangeProps={handleChange}
/>
</Form.Group>
<Form.Group size="lg" controlId="password">
<FloatLabelTextBox
inputLabel="PASSWORD"
inputAutoFocus=""
inputType="password"
inputName="password"
inputPlaceholder="Password"
inputValue={state.password}
handleChangeProps={handleChange}
/>
</Form.Group>
<LoaderButton
block
size="lg"
type="submit"
isLoading={isLoading}
disabled={!validateForm()}>
Login
</LoaderButton>
<p>Not a member? <NavLink to="/register">Get Started Here</NavLink></p>
</Form>
</div>
</div>
<div className="Login-Image">
<img src={LoginImage} />
</div>
</div>
</div>
);
}
export default function ProtectedRoute({ children, ...props }) {
const { isAuthenticated } = useAppContext();
return (
<Route
{...props}
render={props => (
isAuthenticated ?
{children} :
<Redirect to='/login' />
)}
/>
);
}
Your problem is here:
<Route
{...props}
render={props => (
isAuthenticated ?
{children} : // <=== HERE!
<Redirect to='/login' />
)}
You are using {children} as if you were in "JSX-land" ... but you're not. You're already inside a set of {} in your JSX ... which means you're in "Javascript-land".
In "Javascript-land", {children} means "make me an object with a single child property called children". When you then try to insert that object into your JSX, React doesn't know what to do with it, and you get your error.
You just want the same code, minus those curly braces:
<Route
{...props}
render={props => (
isAuthenticated ?
children :
<Redirect to='/login' />
)}
P.S. Don't forget that route tags are just tags, so if you're doing this pattern a lot you might want to create an AuthenticatedRoute tag and use it (instead of repeating this ternary).
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 },
}}
/>
)
}
/>
);
};
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 :)
I have connected my ProfileComponent.js with a redux store and then using that state
to render user image but when I refresh the page I get this TypeError and when I did a console check it seems that my component is using user state before it is available as props(it's basically null in Profile props) but that shouldn't happen right?
error
And when I omitted the <img /> element it seems that my Profile is being rendered twice for the first time my user is null and for second my user is updated in props. Can anyone explain to me what's happening?
console
ProfileComponent.js
import React, { Component } from 'react';
import { Container, Row, Col, Card, CardHeader, CardBody, CardFooter, Input, Form, Button } from 'reactstrap';
import {connect} from 'react-redux';
import { uploadImage, fetchUser } from '../redux/ActionCreators';
import { baseUrl } from '../config';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
const mapStateToProps = (state) => {
return {
user: state.auth.user,
}
}
const mapDispatchToProps = (dispatch) => ({
uploadImage: (userId, imageData) => {dispatch(uploadImage(userId, imageData))},
fetchUser: (userId) => {dispatch(fetchUser(userId))}
})
class Profile extends Component {
constructor(props){
super(props);
this.state = {
selectedFile: null
}
this.handleUpload = this.handleUpload.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
}
handleUpload(event){
const data = new FormData()
data.append('image', this.state.selectedFile)
this.props.uploadImage(this.props.match.params.userId, data);
event.preventDefault();
}
onChangeHandler=event=>{
this.setState({selectedFile: event.target.files[0]});
}
componentDidMount() {
console.log('user',this.props.user);
}
render() {
//
return(
<Container className="form-container" >
<Row>
<Col md={{size:6}}>
<Col md={{size:6,offset:3}}>
<div style={{marginBottom:30}}>
<img className="profile-image" src= {`${baseUrl}${this.props.user.image}`}/>
<Form >
<Input type="file" name="image" id="file" onChange={this.onChangeHandler} />
<Button type="submit" color="primary" className="form-control" onClick = {this.handleUpload}>Upload</Button>
</Form>
</div>
</Col>
</Col>
<Col md={6}>
<div className="shadow-container" style={{marginTop:0}}>
<Card>
<CardHeader>
<h2>Profile details</h2>
</CardHeader>
<CardBody>
<h4 style={{color:'grey'}}>{this.props.match.params.userId}</h4>
<h5 style={{color:'grey'}}></h5>
<small><i></i></small>
</CardBody>
<CardFooter>
<Row>
<Col>
<a href="/editprofile"><span className="fa fa-pencil fa-2x ml-auto"
style={{color:'blue', display:'flex',
flexDirection:'row',justifyContent:'flex-end'}}></span></a>
</Col>
<Col>
<span className="fa fa-trash fa-2x" style={{color:'red'}}></span>
</Col>
</Row>
</CardFooter>
</Card>
</div>
</Col>
</Row>
</Container>
);
}
}
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Profile));
MainComponent.js
import React, {Component} from 'react';
import Menu from './MenuComponent';
import Home from './HomeComponent';
import Users from './UsersComponent';
import Register from './RegisterComponent';
import Signin from './SigninComponent';
import Profile from './ProfileComponent';
import Edit from './EditComponent';
import { signin, signout, fetchUser } from '../redux/ActionCreators';
import {connect} from 'react-redux';
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group'
const mapStateToProps = (state) => {
return {
auth: state.auth
}
}
const mapDispatchToProps = (dispatch) => ({
signin: (credentials) => {dispatch(signin(credentials))},
signout: () => {dispatch(signout())},
fetchUser: (userId) => {dispatch(fetchUser(userId))}
})
class Main extends Component {
componentDidMount() {
this.props.fetchUser(this.props.auth.userId);
console.log('main',this.props.auth);
}
render() {
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
this.props.auth.authenticated
? <Component {...props} />
: <Redirect to={{
pathname: '/signin',
state: { from: props.location }
}} authenticated={this.props.auth.authenticated} />
)} />
)
return (
<div>
<Menu authenticated={this.props.auth.authenticated} signout={this.props.signout} userId={this.props.auth.userId}/>
<TransitionGroup>
<CSSTransition key={this.props.location.key} classNames="page" timeout={300}>
<Switch>
<Route path="/home" component={Home} />
<PrivateRoute path="/users" component={Users} />
<Route path="/signup" component={Register} />
<Route path="/signin" component={() => <Signin signin={this.props.signin} authenticated={this.props.auth.authenticated}/>} />} />
<PrivateRoute path="/profile/:userId" component={() => <Profile />} />
<Route path="/editprofile" component={Edit} />
<Redirect to="/home" />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));
First, check if the value is available or not,
You can do something like this to solve the error :
{this.props.user && <img className="profile-image" src= {`${baseUrl}${this.props.user.image}`}/> }
Issue :
user props might be initialized from some API or after some process, so it's not available for the first render, but as soon as props changes ( your user initialized ), it forces to re-render, so you get the data in the second time.
In the error received, while receiving the data from the API with Fetch, it receives the data as null and cannot read it because it cannot receive the data. To prevent this, the data to be listed is checked with a condition (&&).
{user && <div style={{display : 'flex', flex-direction : 'row', justify-content :'center', width : '100%'}}>
<div style={{width :'60%'}}>
<img style={{width : '%90',}} src={user.images[1]} alt={user.title}/>
</div>
<div>
<h1 style={{fontSize :'48px' , font-weight : 'bold'}} >{user.title} </h1>
<h2 style={{fontSize :'36px'}}> {user.brand}</h2>
<h2 style={{fontSize :'36px'}}>Discount: {user.discountPercentage}</h2>
<h2 style={{fontSize :'36px'}}>Price: {user.price}</h2>
<h6 style={{fontSize :'24px'}}>Stock: {user.stock}</h6>
</div>
</div>}
In the form you should give the name="Image"
and the same name should be passed in router.post
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} />