I'm try to learn and develop React Redux app. In the app I have some private routes. If the user goes to the private routes he should be authenticated with LogIn component and then redirected to the initial route.
The problem is that after the user submits the form and gets authenticated, the reducer doesn't call the render method of LogIn component.
I'm stuck and can't figure out the reason of this.
// ../ClientApp/src/App.js
import React from 'react';
import { Route } from 'react-router';
import { Redirect } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './components/Home';
import Block from './components/Block';
import LogIn from './components/LogIn';
export const auth = {
isAuthenticated: false
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
auth.isAuthenticated
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
export default () => (
<Layout>
<Route exact path='/' component={Home} />
<PrivateRoute path='/block1' component={Block} />
<PrivateRoute path='/block2' component={Block} />
<PrivateRoute path='/block3' component={Block} />
<PrivateRoute path='/block4' component={Block} />
<PrivateRoute path='/block5' component={Block} />
<PrivateRoute path='/block7' component={Block} />
<Route path='/login' component={LogIn} />
</Layout>
);
// ../ClientApp/src/components/LogIn.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import { bindActionCreators } from 'redux';
import './LogIn.css';
import { actionCreators } from '../store/LogIn';
import { Redirect } from 'react-router-dom';
import { auth } from '../App';
class LogIn extends Component {
state = {
credentials: {
username: '',
password: ''
},
error: ''
}
dismissError = () => {
this.setState({ error: '' });
}
handleChange = e => {
const credentials = this.state.credentials;
credentials[e.target.name] = e.target.value;
this.setState({ credentials: credentials });
}
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.credentials.username) {
return this.setState({ error: 'This field is required' });
}
if (!this.state.credentials.password) {
return this.setState({ error: 'This field is required' });
}
this.props.requestLogIn(this.state.credentials);
}
render() {
auth.isAuthenticated = this.props.isAuthenticated;
const { credentials } = this.state;
if (this.props.redirectToReferrer) {
const { from } = this.props.location.state || {
from: { pathname: '/' }
}
return (
<Redirect to={from} />
)
}
return (
<div className="container">
<div className="row">
<div className="col-md-6 col-md-offset-3">
<div className="panel panel-login">
<div className="panel-heading">
<div className="row">
<div className="col-xs-6">
Log in
</div>
</div>
<hr />
</div>
<div className="panel-body">
<div className="row">
<div className="col-lg-12">
<form id="login-form" onSubmit={this.handleSubmit} style={{ display: 'block' }}>
{
this.state.error &&
<h3 data-test="error" onClick={this.dismissError}>
<button onClick={this.dismissError}>X</button>
{this.state.error}
</h3>
}
<div className="form-group">
<input
type="text"
name="username"
tabIndex="1"
className="form-control"
placeholder="E-mail"
value={credentials.username}
onChange={this.handleChange} />
</div>
<div className="form-group">
<input
type="password"
name="password"
tabIndex="2"
className="form-control"
placeholder="Password"
value={credentials.password}
onChange={this.handleChange} />
</div>
<div className="form-group">
<div className="row">
<div className="col-sm-6 col-sm-offset-3">
<input
type="submit"
tabIndex="3"
className="form-control btn btn-login"
value="Log in" />
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.isAuthenticated,
redirectToReferrer: state.redirectToReferrer
}
}
export default connect(
mapStateToProps,
dispatch => bindActionCreators(actionCreators, dispatch)
)(LogIn);
// ../ClientApp/src/store/LogIn.js
const authenticated = 'AUTHENTICATED_USER';
const unauthenticated = 'UNAUTHENTICATED_USER';
const authenticationError = 'AUTHENTICATION_ERROR';
const initialState = {
isAuthenticated: false,
redirectToReferrer: false,
error: '',
token: ''
}
export const actionCreators = {
requestLogIn: ({ username, password }) => async (dispatch) => {
try {
const response = await fetch('api/Authentication/Authenticate',
{
method: 'POST',
body: JSON.stringify({
username: username,
password: password
}),
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin'
});
const token = await response.text();
dispatch({
type: authenticated,
token
});
} catch (e) {
console.log(e);
dispatch({
type: authenticationError,
error: 'Invalid email or password'
});
}
}
}
export const reducer = (state, action) => {
state = state || initialState;
switch (action.type) {
case authenticated:
return {
...state,
isAuthenticated: true,
redirectToReferrer: true,
token: action.token
};
case unauthenticated:
return { ...state, isAuthenticated: false };
case authenticationError:
return { ...state, isAuthenticated: false, error: action.error };
}
return state;
}
UPDATE:
Thanks to remix23's answer. He was right that I had several reducers and I had to point logIn reducer in the mapStateToProps function like this:
const mapStateToProps = state => {
return {
isAuthenticated: state.logIn.isAuthenticated,
redirectToReferrer: state.logIn.redirectToReferrer,
error: state.logIn.error,
token: state.logIn.token
}
}
Just for your information (perhaps it can be usefull for someone) here's my reducer configuration:
//.. /ClientApp/src/store/configureStore.js:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Items from '../reducers/items';
import * as LogIn from './LogIn';
export default function configureStore(history, initialState) {
const reducers = {
items: Items.reducer,
logIn: LogIn.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
The path // ../ClientApp/src/store/LogIn.js suggests that you may have several reducers defined under the store folder.
This usually implies that you also have an "app" reducer (a combination of all your reducers with a key for each of them).
If that the case and the key for your login reducer is login, then in the mapStateToProps you provided, you may have to access the isAuthenticated value this way (otherwise state.isAuthenticated will stay undefined):
const mapStateToProps = state => {
return {
isAuthenticated: state.login.isAuthenticated,
...
}
}
Also as others have suggested, accessing auth from the initial value of your auth store is bad, even if as it looks it may work because you set it in the Login component.
You should connect App like you did with Login and access isAuthenticated through props (and never set a value of the initial states of your stores).
Related
*Sorry in advance I couldn't split the code into separate components in the post
I have a question that I will be able to explain well
I have an authentication component that receives a prop from another component
Now when I put all the components under one big parent component that contains all the components everything works fine and the props pass successfully
But as soon as I start using route I get this message
"Uncaught (in promise) TypeError: this.props.displaychange is not a function"
whet I tried to do here is to change the state in the TeamList component (state.displayTeams to true)
import './App.css';
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import Home from './components/Home/Home';
import LogIn from './components/Log-in/Log-in';
import SignIN from './components/Sign-in/Sign-in';
import TeamList from './components/Team-list/Team-list';
import TeamDisplay from './components/TeamDisplay/TeamDisplay';
function App() {
return (
<div>
<Routes>
<Route path='/' exact element={<Home />} />
<Route path='/login' element={<LogIn />} />
<Route path='/sign-in' element={<SignIN />} />
<Route path='/teamlist' element={<TeamList />} />
<Route path='/teamdisplay' element={<TeamDisplay />} />
</Routes>
</div>
);
}
export default App;
import React, { Component } from "react";
import { signInWithEmailAndPassword, signOut } from 'firebase/auth'
import { auth } from '../../firebase-config'
import './Log-in.css'
import { Link } from "react-router-dom";
class LogIn extends Component {
state = {
logEmail: '',
logPass: '',
user: '',
login: false
}
login = async (displaychange, e) => {
e.preventDefault()
this.setState({ login: true })
const user = await signInWithEmailAndPassword(auth, this.state.logEmail, this.state.logPass)
console.log(user.user.email)
this.setState({ user: user.user.email })
this.setState({ logEmail: '', logPass: '' })
console.log('logged in')
this.props.displaychange()
}
logOut = async (displaychangeOut, e) => {
e.preventDefault()
if (this.state.login) {
await signOut(auth)
this.setState({ user: '', logEmail: '', logPass: '' })
console.log('you out')
this.props.displaychangeOut()
} else {
return
}
}
render() {
return (
<div className="Login-form">
<form>
<span>ACCOUNT LOGIN</span>
<label>USERNAME </label>
<input value={this.state.logEmail} onChange={(e) => this.setState({ logEmail: e.target.value })} name="nameLog" placeholder="Your name..." />
<label>PASSWORD </label>
<input value={this.state.logPass} onChange={(e) => this.setState({ logPass: e.target.value })} name="passLog" placeholder="Your password..." />
<button onClick={(e) => this.login(this.props.displaychange, e)}>LOG IN</button>
</form>
</div>
)
}
}
export default LogIn
import React, { Component } from "react";
import { db } from '../../firebase-config'
import { collection, addDoc, getDocs, getDoc, doc } from 'firebase/firestore'
import TeamDisplay from "../TeamDisplay/TeamDisplay";
import LogIn from "../Log-in/Log-in";
const teamCollectionRef = collection(db, 'Teams')
class TeamList extends Component {
state = {
teams: [
],
displayTeams: false
}
componentDidMount() {
getDocs(teamCollectionRef)
.then(snap => {
let teams = []
snap.docs.forEach(doc => {
teams.push({ ...doc.data() })
});
this.setState({ teams: teams });
});
}
changedisplay = () => {
this.setState({ displayTeams: true })
}
changedisplayOut = () => {
this.setState({ displayTeams: false })
}
render() {
let displayTeam
if (this.state.displayTeams) {
displayTeam = this.state.teams.map(item => <TeamDisplay key={item.teamId} team={item} />)
} else {
displayTeam = null
}
return (
<div>
{displayTeam}
</div>
)
}
}
export default TeamList
I am getting this error and I don;t know what else to do.
I am using next.js and my code looks like this.
The _app.js:
import '../styles/globals.scss'
import React from 'react'
import Layout from '../components/Layout'
import Head from "next/head";
import Signin from "./signin";
import Register from "./register";
import { DataProvider } from "../store/GlobalState";
function MyApp ({
Component,
pageProps
}) {
if (typeof window !== 'undefined') {
if (window.location.pathname === '/signin') {
return (
<DataProvider>
<Signin/>
</DataProvider>
)
} else if (window.location.pathname === '/register') {
return (
<DataProvider>
<Register/>
</DataProvider>
)
}
}
return (
<DataProvider>
<Head>
<title>Above the Sky</title>
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
</DataProvider>
)
}
export default MyApp
I am doing this because I want the register and the login pages to be separate from the layout, not having any header or footer whatsoever... If you have a hint on this , how I should do this better please tell me .... but this is not the main problem..
and now the Register.js:
import Head from 'next/head'
import { useContext, useEffect, useState } from "react";
import Link from 'next/link'
import valid from '../utils/valid'
import { DataContext } from "../store/GlobalState";
const Register = () => {
const [ mounted, setMounted ] = useState(false);
const initialState = {
email: '',
password: '',
cf_password: ''
};
const [ userData, setUserData ] = useState(initialState);
const {
email,
password,
cf_password
} = userData;
const {
state,
dispatch
} = useContext(DataContext)
const handleChangeInput = e => {
const {
name,
value
} = e.target
setUserData({
...userData,
[name]: value
})
dispatch({
type: 'NOTIFY',
payload: {}
})
}
const handleSubmit = async e => {
e.preventDefault()
const errorMessage = valid(email, password, cf_password)
if (errorMessage) {
return dispatch({
type: 'NOTIFY',
payload: { error: errorMessage }
})
}
dispatch({
type: 'NOTIFY',
payload: { success: 'Ok' }
})
}
useEffect(() => {
setMounted(true)
}, [])
return (
mounted
&&
<div style={{
backgroundColor: 'black',
height: '100vh'
}}>
<Head>
<title>Register Page</title>
</Head>
<div className="login-dark" style={{ height: "695px" }}>
<form className='container' onSubmit={handleSubmit}>
<div className="illustration"><i className="fas fa-thin fa-user-plus"/></div>
<div className="mb-3">
<label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
<input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
name="email" value={email} onChange={handleChangeInput}/>
<div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword1" className="form-label">Password</label>
<input type="password" className="form-control" id="exampleInputPassword1"
name="password" value={password} onChange={handleChangeInput}/>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword2" className="form-label">Confirm Password</label>
<input type="password" className="form-control" id="exampleInputPassword2"
name="cf_password" value={cf_password} onChange={handleChangeInput}/>
</div>
<div className='button-container'>
<button type="submit" className="btn btn-primary btn-block">Register</button>
</div>
<a className="forgot" href="#">Forgot your email or password?</a>
<p className="have-account">Already have an account ? <Link href="/signin"><a style={{ color: 'crimson' }}>Login here</a></Link></p>
</form>
</div>
</div>
)
}
export default Register
When I render the register page I get this error in the console ..
"next-dev.js?3515:32 Warning: Did not expect server HTML to contain a in ."
These are my store files aswell:
Actions.js
export const ACTIONS = {
NOTIFY: 'NOTIFY',
AUTH: 'AUTH'
}
Reducer.js
import { ACTIONS } from './Actions';
const reducers = (state, action) => {
switch (action.type) {
case ACTIONS.NOTIFY:
return {
...state,
notify: action.payload
};
case ACTIONS.AUTH:
return {
...state,
auth: action.payload
};
default:
return state;
}
}
export default reducers
and the GlobalState.js
import { createContext, useReducer } from "react";
import reducers from "./Reducers";
export const DataContext = createContext()
export const DataProvider = ({ children }) => {
const initialState = {
notify: {},
auth: {}
}
const [ state, dispatch ] = useReducer(reducers, initialState)
const { cart, auth } = state
return (
<DataContext.Provider value={{
state,
dispatch
}}>
{children}
</DataContext.Provider>
)
}
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 React-redux application, and I want to implement User Login/Sign Up. For login/sign up I use Django API. I want to create some public and private routes as per user login.
Below is my code:
LoginComponent.jsx
import React, { Component } from "react";
import RightPanel from "../AdBanner/RightPanel";
import Panel from "react-bootstrap/es/Panel";
import { connect } from "react-redux";
import * as actions from "../../store/actions/auth";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
};
}
handleUsernameChange = e => {
this.setState({ username: e.target.value });
};
handlePasswordChange = e => {
this.setState({ password: e.target.value });
};
handleSubmit = e => {
e.preventDefault();
this.props.onAuth(
this.state.username,
this.state.password,
"",
"",
"0",
"",
"0"
);
console.log(this.props.isAuthenticated);
if (this.props.isAuthenticated) this.redirectToHome();
else {
this.setState({ errorMsg: "Bad credentials!" });
setTimeout(() => {
this.setState({ errorMsg: "" });
}, 3000);
}
};
redirectToHome = () => <Redirect to={{ pathname: "/" }} />;
render() {
let errorMessage = null;
if (this.props.error) {
errorMessage = <p>{this.props.error}</p>;
}
return (
<section className="body_panel">
<div className="container">
<div className="row">
<div className="col-md-7 padding-lef">
<div className="section-title">
<h3>MySite</h3>
<h1>User Login</h1>
</div>
<div className="inner-content" id="login-panel">
{errorMessage}
<Panel>
<Panel.Body>
<div className="col-sm-12">
<label className="col-sm-4">Email-ID:</label>
<div className="col-sm-8">
<input
type="text"
className="form-control"
onChange={this.handleUsernameChange}
/>
</div>
</div>
<div className="col-sm-12">
<label className="col-sm-4">Password:</label>
<div className="col-sm-8">
<input
type="password"
className="form-control"
onChange={this.handlePasswordChange}
/>
</div>
</div>
<div
className="col-md-12 no-padding"
style={{ marginTop: "20px" }}
>
<div className="col-sm-4" />
<div className="col-sm-8">
<button
type="button"
className="btn"
onClick={this.handleSubmit}
>
Login
</button>
</div>
</div>
</Panel.Body>
</Panel>
</div>
</div>
<RightPanel />
</div>
</div>
</section>
);
}
}
const mapStateToProps = state => {
return {
loading: state.loading,
error: state.error
};
};
const mapDispatchToProps = dispatch => {
return {
onAuth: (
email,
password,
user_android_id,
user_fcm_token,
user_social_flag,
user_name,
user_fb_id
) =>
dispatch(
actions.authLogin(
email,
password,
user_android_id,
user_fcm_token,
user_social_flag,
user_name,
user_fb_id
)
)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
store/actions/auth.jsx
import * as actionTypes from "./actionTypes";
import axios from "axios";
export const authStart = () => {
return {
type: actionTypes.AUTH_START
};
};
export const authSuccess = (token, user) => {
return {
type: actionTypes.AUTH_SUCCESS,
token: token,
user: user
};
};
export const authFail = error => {
return {
type: actionTypes.AUTH_FAIL,
error: error
};
};
export const logout = () => {
localStorage.removeItem("user");
localStorage.removeItem("token");
localStorage.removeItem("expirationDate");
return {
type: actionTypes.AUTH_LOGOUT
};
};
//method to check expiration date
export const checkAuthTimeout = expirationTime => {
return dispatch => {
setTimeout(() => {
dispatch(logout);
}, expirationTime * 1000);
};
};
export const authLogin = (
email,
password,
user_android_id,
user_fcm_token,
user_social_flag,
user_name,
user_fb_id
) => {
return dispatch => {
dispatch(authStart());
axios
.post("http://api.mydomain.in/user-login/", {
email: email,
password: password,
user_android_id: user_android_id,
user_fcm_token: user_fcm_token,
user_social_flag: user_social_flag,
user_name: user_name,
user_fb_id: user_fb_id
})
.then(res => {
// console.log(res.data);
if (res.data.result === 1) {
const token = res.data.key;
const user = res.data.user_id;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000); //time duaryion of 1hour
// logic to store user session
// we need to store in something that persist
// so we store in localstorage
localStorage.setItem("token", token);
localStorage.setItem("user", user);
localStorage.setItem("expirationDate", expirationDate);
dispatch(authSuccess(token, user));
dispatch(checkAuthTimeout(3600));
} else {
// console.log("user fail");
dispatch(authFail("User not registered"));
}
})
.catch(err => {
dispatch(authFail(err));
});
};
};
export const authSignup = (
email,
password1,
password2,
username,
phone_no,
user_android_id,
user_fcm_token,
user_social_flag,
user_fb_id,
user_android_app_version
) => {
return dispatch => {
dispatch(authStart());
axios
.post("http://api.mydomain.in/registration/", {
email: email,
password1: password1,
password2: password2,
name: username,
phone_no: phone_no,
user_android_id: user_android_id,
user_fcm_token: user_fcm_token,
user_social_flag: user_social_flag,
user_fb_id: user_fb_id,
user_android_app_version: user_android_app_version
})
.then(res => {
const token = res.data.key;
const user = res.data.user_id;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000); //time duration of 1hour
// logic to store user session
// we need to store in something that persist
// so we store in localstorage
localStorage.setItem("token", token);
localStorage.setItem("user", user);
localStorage.setItem("expirationDate", expirationDate);
dispatch(authSuccess(token, user));
dispatch(checkAuthTimeout(3600));
})
.catch(err => {
dispatch(authFail(err));
});
};
};
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem("token");
const user = localStorage.getItem("user");
if (token === undefined && user === undefined) {
dispatch(logout());
} else {
const expirationDate = new Date(localStorage.getItem("expirationDate"));
if (expirationDate <= new Date()) {
dispatch(logout());
} else {
dispatch(authSuccess(token, user));
dispatch(
checkAuthTimeout(
(expirationDate.getTime() - new Date().getTime()) / 1000
)
);
}
}
};
};
App.jsx
import React, { Component } from "react";
import { Router, Route, Switch } from "react-router-dom";
import { Helmet } from "react-helmet";
import Header from "./components/Header_footer/Header";
import Footer from "./components/Header_footer/Footer";
import AdBannerTop from "./components/AdBanner/AdBannerTop";
import AdBannerLeft from "./components/AdBanner/AdBannerLeft";
import Login from "./components/Auth/Login";
import ProtectedPage from "./components/protected/ProtectedPage";
import ReactGA from "react-ga";
import * as actions from "./store/actions/auth";
import { connect } from "react-redux";
import { PrivateRoute } from "./components/PrivateRoute";
import createBrowserHistory from "history/createBrowserHistory";
const history = createBrowserHistory();
ReactGA.initialize("UA-*******-2");
history.listen((location, action) => {
ReactGA.pageview(location.pathname + location.search);
});
class App extends Component {
componentDidMount() {
this.props.onTryAutoSignup();
ReactGA.pageview(window.location.pathname + window.location.search);
}
componentDidUpdate = () =>
ReactGA.pageview(window.location.pathname + window.location.search);
render() {
return (
<Router history={history}>
<React.Fragment>
<Helmet>
<title>My site</title>
<meta name="description" content="my site description" />
</Helmet>
<Header {...this.props} />
<AdBannerTop />
<AdBannerLeft />
<Switch>
<PrivateRoute exact path="/protected-page/" component={ProtectedPage}
/>
<Route exact path="/user-login/" component={Login} />
</Switch>
<Footer />
</React.Fragment>
</Router>
);
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.token !== null,
user: state.user == null ? null : state.user
};
};
const mapDispatchToProps = dispatch => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { BrowserRouter } from "react-router-dom";
import { createStore, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { Provider } from "react-redux";
import reducer from "./store/reducers/auth";
require("dotenv").config();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
const app = (
<Provider store={store}>
<App />
</Provider>
);
ReactDOM.render(app, document.getElementById("root"));
serviceWorker.unregister();
My problem:
Want to create Public Routes and Private Routes (only accessible when user logs in). If a user clicks on any private route, instead of redirecting to login page without any message, I want the user should get a message something like: "You need to login to access this". instead my private route has a redirection like:
<Route
{...rest}
render={props =>
localStorage.getItem("user") ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/user-login", state: { from: props.location } }}
/>
)
}
/>
also, on successful login user should be redirected to the page where he came from, instead navigating to home page.
Please suggest a better way of this.
Thanks in advance.
You need to integrate redux-thunk with your redux.
https://github.com/reduxjs/redux-thunk
Without thunking you cannot handle conditions on authCheckState function. Default redux dispatch will just resolve the action immediately.
I am trying to redirect a user after login to home page, the redirect happens (this I know because the url changes and I go to a new page), but the content of the page doesn't load. I keep on getting this error
Warning: You tried to redirect to the same route you're currently on: "/index"
Here's my code for Login.js:
import React, { Component } from 'react';
import '../App.css';
import 'whatwg-fetch';
import { connect } from 'react-redux';
import { loginUser } from './../store/actions/loginActions';
import { Redirect, withRouter } from 'react-router-dom';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
Email: '', Password: '', isloggedin: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
this.props.loginUser(this.state)
}
render() {
const { authError, token } = this.props;
if(token) return <Redirect to="/index" /> // I'm telling it to redirect if the state has a token
return (
<div className="Background">
<Card
title="Login"
style={{ width: 400 }}
>
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} name="Email" type="text" onChange={this.handleChange} />
</Form.Item>
<Form.Item>
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" name="Password" onChange={this.handleChange} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disable='notloggedin' className="login-form-button">
Log in
</Button>
</Form.Item>
</Form>
<div>
{ authError ? <p> { authError } </p> : null }
</div>
</Card>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
authError: state.auth.authError,
token: state.auth.token
}
}
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (data) => dispatch(loginUser(data))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps) (Login));
My action is:
export const loginUser = (data) => {
return (dispatch, getState) => {
fetch(url, {
credentials: 'include',
method: 'POST',
headers: headers,
body: JSON.stringify(data)})
.then(res => { if (res.ok) {
return res.json() }
else {
return Promise.reject({
status: res.status,
statusText: res.statusText
})
}
})
.then(function(response) {
dispatch({type: 'LOGIN_USER', value: response.AccessToken });
})
.catch((err) => {
dispatch({type: 'LOGIN_USER_FAILED', err });
})
}
};
and my App.js is
import React, { Component } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import './App.css';
import Login from './components/Login';
import Home from './components/Home';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<h3 className="App-logo"> App Load </h3>
</header>
<BrowserRouter>
<Switch>
<Route path="/" component={Login} />
<Route path="/index" component={Home} />
</Switch>
</BrowserRouter>
);}}
export default App;
Could anyone please let me know if they see any reason as to why the redirect is working but not loading the page components? I'll really appreciate any pointers.
try to wrap you whole App component into BrowserRouter
I shifted this <Route path="/" component={Login} /> to the bottom of my routes in App.js and it worked.