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.
Related
I am struggling with Login page.
This is the actions/login.js:
export const login = (username, password) => (dispatch) => {
return AuthService.login(username, password).then(
(data) => {
debugger;
dispatch({
type: LOGIN_SUCCESS,
payload: { user: data },
});
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
This is my AuthService.js :
import {BASE_URL} from "../constants/globalConstants";
import axios from "axios";
export const USER_INFO = 'USER_INFO';
const loginEndpoint = BASE_URL + "authenticate";
class AuthService {
login(username, password) {
debugger;
return axios
.post(BASE_URL + "authenticate", { username, password })
.then((response) => {
if (response.data.jwtToken) {
localStorage.setItem(USER_INFO, JSON.stringify(response.data));
}
return response.data;
});
}
logout() {
localStorage.removeItem(USER_INFO);
}
register(username, email, password) {
return axios.post(BASE_URL + "register", {
username,
email,
password,
});
}
}
export default new AuthService();
And finally the Login.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from "react-router-dom";
import { Container, Row, Col, Card, CardBody, FormGroup, Label, Input, Button } from "reactstrap";
import { AvForm, AvField } from "availity-reactstrap-validation";
import axios from 'axios'
import { bindActionCreators } from "redux";
import { selectedSidebarStyle } from "../../actions/sidebarStyleAction";
import { connect } from "react-redux";
import tokenIsValid from './authrorization/JwtAuthorization'
import './../../static/css/Auth.css'
import { BASE_URL } from "../../constants/globalConstants";
import AuthService from "../../services/AuthService";
import { login } from "../../actions/auth";
export const USER_NAME_SESSION_ATTRIBUTE_NAME = 'authenticatedUser';
export const JWT_AUTH_TOKEN = 'AUTH_TOKEN';
export const USER_INFO = 'USER_INFO';
const style = { border: '1px solid #FB3E3E' }
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
userAuth: false,
loading: false,
}
}
handleFieldChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
// this.props.history.push(`/welcome/${this.state.username}`)
requestLogin = () => {
const loginEndpoint = BASE_URL + "authenticate";
axios({
method: 'post',
url: loginEndpoint,
data: {
username: this.state.username,
password: this.state.password
}
}).then((response) => {
if (response.data !== null) {
sessionStorage.setItem(USER_INFO, JSON.stringify(response.data));
}
}, (error) => {
console.log("Unsuccessful login request")
})
}
authHeader() {
const user = JSON.parse(localStorage.getItem(USER_INFO));
if (user && user.jwtToken) {
return { Authorization: 'Bearer ' + user.jwtToken };
} else {
return {};
}
}
isUserLoggedIn() {
let user = window.sessionStorage.getItem(USER_INFO)
if (user === null) {
return false
}
return true;
}
getLoggedInUserName() {
let user = window.sessionStorage.getItem(USER_INFO)
if (user === null) {
return ''
}
return user
}
/*
* TODO: See where to use the logout and how to redirect the user to the login page in case JWT token is expired
* */
logout() {
sessionStorage.removeItem(USER_INFO);
}
handleSubmit = (e) => {
e.preventDefault();
const {dispatch} = this.props;
dispatch(login(this.state.username, this.state.password))
.then(() => {
window.location.reload();
})
.catch(() => {
this.setState({
loading: false
});
});
}
render() {
return (
<React.Fragment>
<div className="account-home-btn d-none d-sm-block">
<Link to="/" className="text-white"><i className="mdi mdi-home h1"></i></Link>
</div>
<section className="bg-account-pages height-100vh">
<img className={"hive-logo1"} src={require('./hive-logo.png')} alt="Logo" width="70px" height="60px" />
<div className="display-table">
<div className="display-table-cell">
<Container>
<Row className="justify-content-center">
<Col lg={5}>
<Card className="account-card">
<CardBody>
<div className="text-center mt-3">
<h3 className="font-weight-bold"><a href=""
className="text-dark text-uppercase account-pages-logo">Sign In</a>
</h3>
<u><p className="text-muted">Enter your credentials to continue to the platform.</p></u>
</div>
<div className="p-3">
<AvForm onSubmit={this.handleSubmit}>
<FormGroup>
<Label htmlFor="username">Email</Label>
<AvField type="text" name="username" value={this.state.email}
onChange={this.handleFieldChange} required className="form-control"
id="username"
placeholder="Enter email" />
</FormGroup>
<FormGroup>
<Label htmlFor="userpassword">Password</Label>
<AvField type="password" name="password" value={this.state.password}
onChange={this.handleFieldChange} required className="form-control"
id="userpassword" placeholder="Enter password" />
</FormGroup>
<div className="custom-control custom-checkbox">
<Input type="checkbox" className="custom-control-input" id="customControlInline" />
<Label className="custom-control-label" htmlFor="customControlInline">Remember
me</Label>
</div>
<div className="mt-3">
<Button color="none" type="submit" className="sign-in-button" >Sign In</Button>
</div>
<div className="mt-4 mb-0 text-center">
<Link to="password_forget" className="text-dark"><i className="mdi mdi-lock"></i> Forgot
your password?</Link>
</div>
</AvForm>
</div>
</CardBody>
</Card>
</Col>
</Row>
</Container>
</div>
</div>
</section>
</React.Fragment>
);
}
}
Login.PropTypes = {
dispatch: PropTypes.func,
login: PropTypes.func
};
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
login
}, dispatch)
};
}
const mapStateToProps = (state) => {
const { isLoggedIn } = state.auth;
const { message } = state.message;
return {
isLoggedIn,
message
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
And I made so many changes and I can't fix this:
enter image description here
I am trying to push the login details, fetched from the bckend to the Session Storage and push it to the Redux so I can fetch the data later after loging and keep the token, id, password and email for the user
Somewhere in the documentation I have read that if we use mapDispatchToProps function in the connect method then the component will not get dispatch function as props .I tried finding the document link but could not get it.
try debugging component props to see dispatch function is there or not
You are already binding login with the dispatch.
So to call that, you need to do this;
this.props.login(...)
instead of this;
dispatch(login(...))
Functions in mapDispatchToProps are added to dispatch and if you call them like this this.props.function_name(), they are dispatched too.
I am creating sign-up and sign-in on a website but I am getting this error TypeError: Cannot read property 'pathname' of undefined I have no idea where this error comes from. I am new to redux So if anyone knows why I get this error please explain to me, so the next time I will know if I still get this error.
Thank you
Here is my code
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import store from './Store';
window.store = store;
ReactDOM.render(
<Provider store= {store}>
<Router>
<React.StrictMode>
<App />
</React.StrictMode>
</Router>
</Provider>,
document.getElementById('root')
);
reportWebVitals();
app.js
import './App.css';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HomePage from './Home_Search_Client/Containers/HomePage';
import About from './Home_Search_Client/Containers/About';
import Feedback from './Home_Search_Client/Containers/Feedback';
import Signup from './Home_Search_Client/Containers/Signup';
import Signin from './Home_Search_Client/Containers/Signin';
import PrivateRoute from './Home_Search_Client/Components/HOC/PrivateRoute';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import { isUserLoggedIn } from './Home_Search_Client/actions';
function App() {
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth);
useEffect(() => {
if(!auth.authenticate){
dispatch(isUserLoggedIn)
}
}, [auth.authenticate]);
return (
<Router>
<Switch>
<PrivateRoute path='/' exact component={HomePage} />
<PrivateRoute path='/about' component={About} />
<PrivateRoute path='/feedback' component={Feedback} />
<Route path='/signup' component={Signup} />
<Route path='/signin' component={Signin} />
</Switch>
</Router>
);
}
export default App;
Store (index.js)
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../Home_Search_Client/reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, composeWithDevTools(
applyMiddleware(thunk)
));
export default store;
auth.actinons.js
import axios from '../helpers/axios';
import { authConstants } from './constants';
export const signin = (user) => {
return async (dispatch) => {
dispatch({ type: authConstants.LOGIN_REQUEST });
const res = await axios.post(`/signin`, {
...user
});
if (res.status === 200) {
const { token, user } = res.data;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
dispatch({
type: authConstants.LOGIN_SUCCESS,
payload: {
token, user
}
});
} else {
if (res.status === 400) {
dispatch({
type: authConstants.LOGIN_FAILURE,
payload: { error: res.data.error }
});
}
}
}
}
export const isUserLoggedIn = () => {
return async dispatch => {
const token = localStorage.getItem('token');
if (token) {
const user = JSON.parse(localStorage.getItem('user'));
dispatch({
type: authConstants.LOGIN_SUCCESS,
payload: {
token, user
}
});
}else{
dispatch({
type: authConstants.LOGIN_FAILURE,
payload: { error: 'Failed to login' }
});
}
}
}
export const signout = () => {
return async dispatch => {
dispatch({ type: authConstants.LOGOUT_REQUEST });
const res = await axios.post(`/signout`);
if (res.status === 200) {
localStorage.clear();
dispatch({ type: authConstants.LOGOUT_SUCCESS });
} else {
dispatch({
type: authConstants.LOGOUT_FAILURE,
payload: { error: res.data.error }
});
}
}
}
user.action.js
import axios from '../helpers/axios';
import { userContants } from './constants';
export const signup = (user) => {
return async (dispatch) => {
dispatch({ type: userContants.USER_REGISTER_REQUEST });
const res = await axios.post(`/signup`, {
...user
});
if (res.status === 201) {
const { message } = res.data;
dispatch({
type: userContants.USER_REGISTER_SUCCESS,
payload: { message }
});
} else {
if (res.status === 400) {
dispatch({
type: userContants.USER_REGISTER_FAILURE,
payload: { error: res.data.error }
});
}
}
}
};
Signup.js(Containers)
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Button, Col, Container, Form, Row } from 'react-bootstrap'
import Layout from '../../Components/Layout'
import Input from '../../Components/UI/Input'
import { signup } from '../../actions'
import { Redirect } from 'react-router'
const Signup = (props) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const auth = useSelector((state) => state.auth)
const user = useSelector((state) => state.user)
const dispatch = useDispatch();
useEffect(() => {
if(!user.loading){
setFirstName('');
setLastName('');
setEmail('');
setPassword('');
}
}, [user.loading]);
const userSignup = (e) => {
e.preventDefault();
const user = {
firstName,
lastName,
email,
password
};
dispatch(signup(user));
};
if(auth.authenticate) {
return <Redirect to={'/'} />
}
if(user.loading) {
return <p>Loading...</p>
}
return (
<Layout>
<Container>
{user.message}
<Row>
<Col md={{ span:6, offset:3 }}>
<Form onSubmit={userSignup}>
<Row>
<Col md = {6}>
<Input
label = 'First Name'
placeholder='First Name'
value= {firstName}
type='text'
onChange={(e) => setFirstName(e.target.value)}
/>
</Col>
<Col md = {6}>
<Input
label = 'Last Name'
placeholder='Last Name'
value= {lastName}
type='text'
onChange={(e) => setLastName(e.target.value)}
/>
</Col>
</Row>
<Input
label='Email'
placeholder='Email'
value={email}
type='email'
onChange={(e) => setEmail(e.target.value)}
/>
<Input
label='Password'
placeholder='Password'
value={password}
type='password'
onChange={(e) => setPassword(e.target.value)}
/>
<Button variant='primary' type='submit' >
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Signup
Signin.js(Containers)
import React, { useState } from 'react'
import { Button, Col, Container, Form, Row } from 'react-bootstrap'
import Layout from '../../Components/Layout';
import Input from '../../Components/UI/Input';
import { useDispatch, useSelector } from 'react-redux';
import { signin } from '../../actions'
import { Redirect } from 'react-router';
const Signin = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const auth = useSelector(state => state.auth);
const dispatch = useDispatch();
const userLogin = (e) => {
e.preventDefault();
const user = {
email, password
}
dispatch(signin(user))
}
if(auth.authenticate) {
return <Redirect to={'/'} />
}
return (
<Layout>
<Container>
<Row>
<Col md={{ span: 6, offset: 3 }}>
<Form onSubmit={userLogin}>
<Input
label='Email'
placeholder='Email'
value={email}
type='email'
onChange={(e) => setEmail(e.target.value)}
/>
<Input
label='Password'
placeholder='Password'
value={password}
type='password'
onChange={(e) => setPassword(e.target.value)}
/>
<Button variant='primary' type='submit'>
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Sign in
axios.js (helpers)
import axios from 'axios';
import { api } from '../../urlConfig';
import store from '../../Store';
import { authConstants } from '../actions/constants';
const token = window.localStorage.getItem('token');
const axiosInstance = axios.create({
baseURL: api,
headers:{
'Authorization': token ? `${token}` : ''
}
});
axiosInstance.interceptors.request.use((req) => {
const { auth } = store.getState();
if(auth.token){
req.headers.Authorization = `${auth.token}`
}
return req;
});
axiosInstance.interceptors.response.use((res) => {
return res;
}, (error) => {
console.log(error.response);
const status = error.response ? error.response.status : 500;
if(status && status === 500) {
localStorage.clear();
store.dispatch({ type: authConstants.LOGOUT_SUCCESS });
}
return Promise.reject(error);
})
export default axiosInstance;
PrivateRoute.js
import React from 'react'
import { Redirect, Route } from 'react-router'
const PrivateRoute = ({ component: Component, ...rest }) => {
return <Route {...rest} component={(props) => {
const token = window.localStorage.getItem('token');
if(token) {
return <Component {...props} />
}else{
return <Redirect to={'signin'} />
}
}} />
}
export default PrivateRoute;
Header.js
import React from 'react';
import Logo from '../../../Logos/main-logo.png';
import { Navbar, Button, Nav } from 'react-bootstrap';
import { NavLink } from 'react-router-dom';
import KeyboardReturnOutlinedIcon from '#material-ui/icons/KeyboardReturnOutlined';
import './style.css';
import { useDispatch, useSelector } from 'react-redux';
import { signout } from '../../actions';
const Header = () => {
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
const logout = () => {
dispatch(signout());
}
const renderLoggedInLinks = () => {
return (
<Nav className='host_btn ml-auto'>
<Nav.Link><NavLink className='NavLink' to={`/`}>Home</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/about`}>About</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/feedback`}>Feedback</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' onClick={logout}>Signout</NavLink></Nav.Link>
<Button className='ml-5'>Become a host <KeyboardReturnOutlinedIcon /> </Button>
</Nav>
);
};
const renderNonLoggedInLinks = () => {
return (
<Nav className='host_btn ml-auto'>
<Nav.Link><NavLink className='NavLink' to={`/`}>Home</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/about`}>About</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/feedback`}>Feedback</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/signup`}>Signup</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/signin`}>Signin</NavLink></Nav.Link>
<Button className='ml-5'>Become a host <KeyboardReturnOutlinedIcon /> </Button>
</Nav>
)
}
return (
<Navbar className='mt-2 ml-2 mr-2' collapseOnSelect expand='sm' bg='light' variant='light'>
<Navbar.Toggle aria-controls='responsive-navbar-nav' />
<Navbar.Collapse id='responsive-navbar-nav' >
<Navbar.Brand className='logo-img' to="#home"><img src={Logo} /></Navbar.Brand>
<Nav className='host_btn ml-auto'>
{auth.authenticate ? renderLoggedInLinks() : renderNonLoggedInLinks()}
</Nav>
</Navbar.Collapse>
</Navbar>
)
}
export default Header
Note: please pull the repo to get access to the full App, there are too many files to simply share the code here. The repo: https://github.com/liviu-ganea/movie-database.git
So I've asked a question previously about my first React + Redux app and I've improved it. I got 2 problems now. The delete function for each movie (i.e. each entry in the redux state) is working just fine. So I now have problems with Login. Every time I press the button, nothing happens, and I suspect it's because the Login page component receives no props from the redux state.
My reasoning:
handleCLick = () => {
let userName = this.props.user.nickname;
let pw = this.props.user.password;
const nicknameBox = document.getElementById('nickname').textContent;
const passwordBox = document.getElementById('password').textContent.trim;
/*if (nicknameBox === userName && passwordBox === pw) {*/
this.props.loginUser((this.props.user.isLoggedIn = true));
this.props.history.push('/');
//}
};
When commented as it is now, it should go to the home page whenever I click the login button and if the password matches doesn't matter. Only there is no reaction to me pressing the button. Is the problem as I suspect?
And another problem: see in Movie.js I've tried to get the path (it's set in state: movies: cover) to the movie poster (located in ./src) so that when (or if) I make use of an API the path should set itself dynamically (i.e. I won't have to go into every component and add the path manually). Same thing on the Home page (./src/Components/Routes/Home.js)...no posters for any movie.
After Comments:
Component:
import React, { Component } from 'react';
import './routes.css';
import { loginAction } from '../../actions/userActions';
import { connect } from 'react-redux';
class Login extends Component {
handleCLick = () => {
let userName = this.props.user.nickname;
let pw = this.props.user.password;
const nicknameBox = document.getElementById('nickname').textContent;
const passwordBox = document.getElementById('password').textContent.trim;
/*if (nicknameBox === userName && passwordBox === pw) {*/
this.props.loginUser((this.props.user.isLoggedIn = true));
this.props.history.push('/');
//}
};
render() {
console.log(this.props);
return (
<div className="container page-container login-page">
<h3 className="page-title">Log In User</h3>
<div className="login-box">
<div>
<label className="login-labels" htmlFor="nickname">
Login{' '}
</label>
<input type="text" className="nickname-box" name="nickname" id="nickname" />
</div>
<div>
<label className="login-labels" htmlFor="password">
Password{' '}
</label>
<input type="password" className="password-box" name="password" id="pasword" />
</div>
<button className="login-button" onClick={this.handleClick}>
Login
</button>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.user,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (isLoggedIn) => {
dispatch(loginAction(isLoggedIn));
},
};
};
export default connect(mapDispatchToProps, mapStateToProps)(Login);
Reducer:
import { createStore } from 'redux';
const initialState = {
movies: [
{
id: '1',
title: 'John Wick Chapter 2',
year: '2017',
main_actor: 'Keanu Reeves',
summary: `The hitman that's been retired then back then retired strikes again, this time against the Mafia.`,
cover: '../john_wick_2.jpg',
},
{
id: '2',
title: 'Star Wars Revenge of the Sith',
year: '2005',
main_actor: 'Ewan McGregor',
summary: `Anakin betrays Space Jesus so General Kenobi is forced to Mustafar Anakin.`,
cover: '../sw_rots.png',
},
{
id: '3',
title: 'Star Wars The Clone Wars',
year: '2008 - 2020',
main_actor: 'Ewan McGregor, Hayden Christensen',
summary: `Yoda has finally overdosed on Ketamine, Mace Window hasn't been defenestrated yet and The Negotiator has proven himself incapable of falling to the Dark Side`,
cover: '../sw_tcw.jpg',
},
],
user: {
isLoggedIn: 'false',
nickname: 'obi_wan',
password: '12345',
},
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'DELETE_MOVIE':
let newMovieList = state.movies.filter((movie) => {
return action.id !== movie.id;
});
return {
...state,
movies: newMovieList,
};
case 'LOG_IN':
let newUserState = action.isLoggedIn;
return {
...state,
user: { ...state.user, isLoggedIn: action.payload.isLoggedIn },
};
default:
return state;
}
return state;
};
export default rootReducer;
userActions:
export const loginAction = (isLoggedIn) => {
return {
type: 'LOG_IN',
isLoggedIn: 'true',
};
};
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers/rootReducer';
import App from './App';
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
console.log(this.props) for the Login component:
[![enter image description here][1]][1]
Movie.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { deleteMovieAction } from '../actions/movieActions';
import './Routes/routes.css';
class Movie extends Component {
handleClick = () => {
this.props.deleteMovie(this.props.movie.id);
this.props.history.push('/');
};
render() {
console.log(this.props);
const isUser = this.props.user.isLoggedIn ? (
<button className="delete-movie-button" onClick={this.handleClick}>
Delete
</button>
) : null;
const theMovie = this.props.movie ? (
<div className="movie-container">
<img src={this.props.movie.cover} alt={this.props.movie.title} className="movie-cover" />
<div className="movie-container-content">
<h2 className="movie-title">{this.props.movie.title}</h2>
<p className="movie-description">{this.props.movie.summary}</p>
<div className="movie-data">
<p className="movie-year">{this.props.movie.year}</p>
<p className="movie-actor">{this.props.movie.main_actor}</p>
</div>
</div>
{isUser}
</div>
) : (
<div className="center">Getting data about the movie. Please wait.</div>
);
return <div className="container page-container">{theMovie}</div>;
}
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.movie_id;
return {
user: state.user,
movie: state.movies.find((movie) => movie.id === id),
};
};
const mapDispatchToProps = (dispatch) => {
return {
deleteMovie: (id) => {
dispatch(deleteMovieAction(id));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Movie);
Movie.js - every movie component
import React, {Component} from 'react'
import { connect } from 'react-redux';
import { deleteMovieAction } from '../actions/movieActions'
import './Routes/routes.css'
class Movie extends Component {
handleClick = () => {
this.props.deleteMovie(this.props.movie.id);
this.props.history.push('/');
}
render () {
const coverino = ''
console.log(this.props);
const isUser = this.props.isLoggedIn ? ( <button className='delete-movie-button' onClick={this.handleClick}>Delete</button> ) : (null)
const theMovie = this.props.movie ? (
<div className='movie-container'>
<img src={this.props.movie.cover} alt={this.props.movie.title} className='movie-cover'/>
<div className='movie-container-content'>
<h2 className='movie-title'>{this.props.movie.title}</h2>
<p className='movie-description'>{this.props.movie.summary}</p>
<div className='movie-data'>
<p className='movie-year'>{this.props.movie.year}</p>
<p className='movie-actor'>{this.props.movie.main_actor}</p>
</div>
</div>
{isUser}
</div>
) : ( <div className="center">Getting data about the movie. Please wait.</div> );
return (
<div className='container page-container'>
{theMovie}
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.movie_id;
return {
isLoggedIn: state.user.isLoggedIn,
movie: state.movies.find(movie => movie.id === id),
}
}
const mapDispatchToProps = (dispatch) => {
return {
deleteMovie: (id) => { dispatch(deleteMovieAction(id))}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Movie)
The page display the entire movie list
import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import './routes.css'
class Home extends Component{
render() {
console.log(this.props);
const { movies } = this.props;
const movieList = movies.length ? ( movies.map(movie => {
return (
<Link to={'/' + movie.id} key={movie.id}>
<div className='movie-container'>
<img src={require(`${movie.cover}`)} alt={movie.title} className='movie-cover'/>
<div className='movie-container-content'>
<h2 className='movie-title'>{movie.title}</h2>
<p className='movie-description'>{movie.summary}</p>
<div className='movie-data'>
<p className='movie-year'>{movie.year}</p>
<p className='movie-actor'>{movie.main_actor}</p>
</div>
</div>
</div>
</Link>
)
}
, )) : (<div className='waiting-for-movies'>Loading. Please wait</div>)
return (
<div className='container page-container'>
{movieList}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies
}
}
export default connect(mapStateToProps)(Home)
neither the require method, nor the simple {this.props...} works
[1]: https://i.stack.imgur.com/51FTy.png
I have a ecommerce store with registration and login. After registration, the token is stored in cookie and authentication state is updated. It is working. But the problem is, when I refresh the page, authentication state is set to null. Please check my store and reducers.
store.js
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState,
composeWithDevTools(applyMiddleware(...middleware)));
export default store;
authReducer.js
import * as types from '../types'
export const authReducer = (state = { token: null }, action) => {
switch (action.type) {
case types.AUTHENTICATE:
return {
...state,
token: action.payload
};
case types.DEAUTHENTICATE:
return {
token: null
};
default:
return state;
}
};
authAction.js
import * as types from '../types'
import axios from 'axios'
import cookie from 'js-cookie';
import * as api from '../../pages/api'
import Router from 'next/router';
export const authenticate = user => async dispatch => {
const res = await axios.post(api.URL_REGISTER, {user})
.then(res => {
if (res.data.response === 200) {
setCookie('token', res.data.data.token);
Router.push('/');
dispatch({
type: types.AUTHENTICATE,
payload: res.data.data.token
})
}
else
dispatch({
type: types.AUTHENTICATE,
payload: res.data
})
}).catch(error => {
console.log(error);
});
}
// gets the token from the cookie and saves it in the store
export const reauthenticate = token => {
return dispatch => {
dispatch({ type: types.AUTHENTICATE, payload: token });
};
};
// removing the token
export const deauthenticate = () => {
return dispatch => {
removeCookie('token');
Router.push('/');
dispatch({ type: types.DEAUTHENTICATE });
};
};
/**
* cookie helper methods
*/
export const setCookie = (key, value) => {
if (process.browser) {
cookie.set(key, value, {
expires: 1,
path: '/'
});
}
};
export const removeCookie = key => {
if (process.browser) {
cookie.remove(key, {
expires: 1
});
}
};
export const getCookie = key => {
return cookie.get(key);
};
Header.js
import React from 'react'
import Link from 'next/link'
import {FontAwesomeIcon} from '#fortawesome/react-fontawesome'
import { faSearch, faShoppingCart, faUserCircle, faBoxOpen, faHeart } from '#fortawesome/fontawesome-free-solid'
import { deauthenticate } from '../../store/actions/authAction';
import { connect } from 'react-redux';
const Header = ({ deauthenticate, isAuthenticated }) => (
<div>
<div className="col-12 col-md-4 col-lg-3">
<div className="text-center text-md-right">
<div className="d-inline loginDrop">
<Link href="/">
<a className="signinBtn mr-5">{!isAuthenticated ? "Sign In" : "My Account"}</a>
</Link>
<div className={!isAuthenticated ? "login-content" : "login-content logout-content"}>
<p> </p>
<div className="login-inner">
<Link href={!isAuthenticated ? "/login" : "/profile"}><a><FontAwesomeIcon icon={faUserCircle} className="mr-2"/> Your Profile</a></Link>
<Link href={!isAuthenticated ? "/login" : "/orders"}><a><FontAwesomeIcon icon={faBoxOpen} className="mr-2 orderIcon"/> Orders</a></Link>
<Link href={!isAuthenticated ? "/login" : "/wishlist"}><a><FontAwesomeIcon icon={faHeart} className="mr-2"/> Whishlist</a></Link>
<div className="otherDrop">
{!isAuthenticated ?
<>
<p className="first">Don't have an account?</p>
<p className="register"><Link href="/register" as="/register"><a>Register</a></Link></p>
<p className="login"><Link href="/login"><a>Login</a></Link></p>
</>
:
<p className="login"><a href="#" onClick={deauthenticate}>Logout</a></p>
}
</div>
</div>
</div>
</div>
<Link href="/">
<a className="cartBtn"><FontAwesomeIcon icon={faShoppingCart} className="mr-xl-1"/> Cart</a>
</Link>
</div>
</div>
</div>
)
const mapStateToProps = state => ({ isAuthenticated: !!state.authentication.token });
export default connect(
mapStateToProps,
{ deauthenticate }
)(Header);
_app.js
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import {createWrapper} from 'next-redux-wrapper'
import store from '../store/store'
class MyApp extends App {
render() {
const {Component, pageProps} = this.props
return (
<Provider store={store}>
<Component {...pageProps}></Component>
</Provider>
)
}
}
const makestore = () => store;
const wrapper = createWrapper(makestore);
export default wrapper.withRedux(MyApp);
How to fix initial state not to be null even after refresh the page. I am really stuck here. Is there any option to fix.
All you need is to persist your redux state across a browser refresh by using redux middleware like redux-persist, ie:
if (isClient) {
store = createStore(
persistReducer(persistConfig, rootReducer),
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
}
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).