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>
)
}
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.
in my CRUD app, the user on the first screen enters their username and then can create, edit, and delete posts. I have an array of users and an array of posts (I'm using Redux). When a user creates a post, their username needs to appear in a sector within the div to identify who posted the post, but I still can't find a way to get the right user who is logged in to show in the div. How could I capture the specific user who is currently logged in?
I thought of doing something like: when the user typed their username and went to the posts screen, all posts would be linked to this user, but I don't know how to do that
postsSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
addPost (state, action) {
state.push(action.payload);
},
editPost(state, action) {
const { id, title, content } = action.payload;
const existingPost = state.find((post) => post.id === id);
if (existingPost) {
existingPost.title = title
existingPost.content = content
}
},
postDeleted(state, action) {
const { id } = action.payload;
const existingPost = state.some((post) => post.id === id);
if (existingPost) {
return state.filter((post) => post.id !== id);
}
},
},
});
export const { addPost, editPost, postDeleted } = postsSlice.actions
export default postsSlice
usersSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: [],
reducers: {
saveUser (state, action) {
state.push(action.payload)
},
}
});
export const { saveUser, replaceUsers } = userSlice.actions;
export default userSlice
mainscreen.js:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { addPost } from '../redux/postsslice'
import Modal from "../components/modal.jsx";
import EditModal from '../components/editmodal.jsx';
function MainScreen() {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: new Date(),
title,
content,
user
})
)
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const handleButton = (e) => {
e.preventDefault()
navigate("/")
}
const [openEditModal, setOpenEditModal] = useState();
const [openModal, setOpenModal] = useState();
console.log({ posts })
return (
<div className="containerMainScreen">
<button onClick={handleButton}>Back</button>
{openModal && <Modal deleteId={openModal} closeModal={setOpenModal} />}
{openEditModal && <EditModal editId={openEditModal} closeModal={setOpenEditModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{posts.slice().reverse().map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<br></br>
<textarea style={{ border: "none" }} value={post.content}></textarea>
</div>
</div>
))}
</div>
);
}
export default MainScreen;
Signup:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name));
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
You can store a current user and maintain a list of all users that ever "signed in".
user.slice.js
const initialState = {
currentUser: '',
users: [],
}
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
saveUser(state, action) {
// person that just signed in
state.currentUser = action.payload;
// list of unique user names
state.users = Array.from(new Set([...state.users, action.payload]));
},
replaceUsers: (state, action) => action.payload
}
});
export const { saveUser } = userSlice.actions;
export default userSlice;
MainScreen.js
Add an author property to each added post, and access the post.author property when mapping the posts.
function MainScreen() {
const dispatch = useDispatch();
const { currentUser } = useSelector((state) => state.user); // <-- current user
const posts = useSelector((state) => state.loadPosts);
...
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(
addPost({
id: uuidV4(),
title,
content,
author: currentUser // <-- current user to post
})
);
setTitle("");
setContent("");
};
...
if (currentUser === "") {
return <Navigate to="/" />;
} else {
return (
<div className="containerMainScreen">
...
{posts.map((post) => (
<div className="boxPost" key={post.id}>
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(post.id);
}}
/>
<FiEdit
onClick={() => {
setOpenEditModal(post.id);
}}
style={{
color: "white",
fontSize: "45px",
paddingLeft: "23px"
}}
/>
</div>
<div id="postowner">
<h3>#{post.author}</h3> // <-- user in post object
<br></br>
<textarea
style={{ border: "none" }}
value={post.content}
></textarea>
</div>
</div>
))}
</div>
);
}
}
export default MainScreen;
i have created a login form where user need to input his email id and OTP. below is my code -
import { useState } from 'react';
import axios from '../api/axios';
const useLogin = () => {
const [user, setUser] = useState(false);
const auth = async (value, OTP) => {
let config = {
method: 'POST',
url: '/api/user/generateToken',
headers: {
Authorization: 'value'
},
data: {
username: value,
password: OTP
}
};
try {
const response = await axios(config);
if (response.data.Status === "Failure") {
throw response.data.Message;
} else {
setUser(true);
return { status: response.data.Status, isAuth: user }
}
} catch (err) {
setUser(false);
return { status: undefined, message: err, isAuth: user };
}
}
return { auth, user };
}
export default useLogin
Everything is working fine here only problem is when i'm calling this function in my component i'll receive isAuth always false. Below is my component code -
import React, { Fragment, useRef, useEffect, useState } from 'react';
import { useLocation, useHistory } from "react-router-dom";
import { css } from "#emotion/core";
import ScaleLoader from "react-spinners/ScaleLoader";
import '../css/login.css';
import '../css/common.css';
import logo from '../assets/engageLogo.png';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import useLogin from './../hooks/useOTP';
const override = css`
display: block;
margin: 0 auto;
border-color: #fff;
`;
const OTP = () => {
const [loading, setLoading] = useState(false);
const [color] = useState("#ffffff");
const [APIResponse, setAPIResponse] = useState(false);
const [APIMessage, setAPIMessage] = useState('')
const login = useLogin();
const location = useLocation();
const history = useHistory();
const inputRef = useRef();
const readRef = useRef();
const buttonRef = useRef();
const schema = Yup.object({
otp: Yup.string().required("OTP is Required")
});
const handleChangeError = () => {
return setAPIResponse(false)
}
const {
handleSubmit,
handleChange,
handleBlur,
touched,
errors,
} = useFormik({
initialValues: {
otp: "",
},
validationSchema: schema,
onSubmit: (values) => {
console.log(JSON.stringify(values));
buttonRef.current.disabled = true;
setLoading(true);
const loginCall = login.auth(location.state.email, values.otp);
loginCall.then(response => {
if (response.status === undefined || response.status === null) {
setLoading(false);
buttonRef.current.disabled = false;
setAPIResponse(true)
setAPIMessage(response.message)
} else {
setLoading(false);
history.push({
pathname: '/dashboard',
state: { email: values.email }
});
}
})
},
});
useEffect(() => {
inputRef.current.focus();
readRef.current.value = location.state.email;
}, [location])
return <Fragment>
<div className="centered-form">
<div className="centered-form__box">
<div className="mb-3 text-center">
<img src={logo} className="img-fluid" width="150" alt="Logo" />
</div>
<form onSubmit={handleSubmit} noValidate>
<div className="mb-3">
<label htmlFor="readEmail" className="form-label">Email</label>
<input
type="text"
name="readEmail"
id="readEmail"
ref={readRef}
className="form-control" readOnly />
</div>
<div className="mb-3">
<label htmlFor="otp" className="form-label">OTP</label>
<input
type="text"
name="otp"
id="otp"
ref={inputRef}
onChange={(e) => { handleChange(e); handleChangeError(e) }}
onBlur={handleBlur}
className="form-control" placeholder="Enter OTP" required />
{touched.otp && errors.otp
? <div className="invalid-feedback">Please enter valid OTP</div>
: null}
{APIResponse
? <div className="invalid-feedback">{APIMessage}</div>
: null}
</div>
<div className="d-grid gap-2">
<button ref={buttonRef} className="btn btn-main">{loading ?
<ScaleLoader color={color} loading={loading} css={override} height={15} /> : <span>Login</span>}</button>
</div>
</form>
</div>
</div>
</Fragment>
}
export default OTP
in response of loginCall i'll always get isAuth: false.
I want to use isAuth for protecting my routes. Just to check whether user has logged in or not.
why setUser is not updating the value here.
thanks in advance...
That's because by the time you returning your isAuth value the new user value is not set yet. you need to know that React setState is asynchronous function.
just use the the boolean itself directly like this:
setUser(true);
return { status: response.data.Status, isAuth: true }
or in case of a rejection:
setUser(false);
return { status: undefined, message: err, isAuth: false };
I want to know how to make a redirection after a case statement. I have been making this code, but after the case statement, nothing happens. I review on the web, but nothing seems to work.When i submit the validated form, it doesn't redirect or refreshes.
Code
import { Redirect } from 'react-router-dom'
import React, { Component } from 'react'
const initState = {}
const adReducer = (state = initState, action) => {
switch (action.type) {
case 'CREATE_AD_SUCCESS':
alert('create ad success');
return <Redirect to='/' /> ;
case 'CREATE_AD_ERROR':
alert('create ad error');
return state;
default:
return state;
}
};
export default adReducer;
adAction.js code
export const createAd = (ad) => {
return (dispatch, getState, {getFirebase,getFirestore}) => {
// make async call to database
const firestore = getFirestore();
const profile = getState().firebase.profile;
const authorId = getState().firebase.auth.uid;
firestore.collection('ads').add({
...ad,
authorFirstName: profile.firstName,
authorLastName: profile.lastName,
authorId: authorId,
createdAt: new Date()
}).then(() => {
dispatch({ type: 'CREATE_AD_SUCCESS' });
}).catch(err => {
dispatch({ type: 'CREATE_AD_ERROR' }, err);
});
}
};
Create ad code :
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createAd } from '../../store/actions/adActions'
import { Redirect } from 'react-router-dom'
import firebase from "firebase";
import FileUploader from "react-firebase-file-uploader";
class CreateAd extends Component {
state = {
title: '',
content: '',
avatar: "",
isUploading: false,
progress: 0,
avatarURL: "",
contactno:""
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.createAd(this.state);
}
handleUploadStart = () => this.setState({ isUploading: true, progress: 0 });
handleProgress = progress => this.setState({ progress });
handleUploadError = error => {
this.setState({ isUploading: false });
console.error(error);
};
handleUploadSuccess = filename => {
this.setState({ avatar: filename, progress: 100, isUploading: false });
firebase
.storage()
.ref("images")
.child(filename)
.getDownloadURL()
.then(url => this.setState({ avatarURL: url }));
};
render() {
const { auth } = this.props;
if (!auth.uid) return <Redirect to='/signin' />
return (
<div className="container">
<form className="white" onSubmit={this.handleSubmit}>
<h5 className="grey-text text-darken-3">Create a New Ad</h5>
<div className="input-field">
<input type="text" id='title' onChange={this.handleChange} />
<label htmlFor="title">Ad Title</label>
</div>
<div className="input-field">
<textarea id="content" className="materialize-textarea" onChange={this.handleChange}></textarea>
<label htmlFor="content">AdContent</label>
</div>
<div className="input-field">
<input type="text" id='contactno' onChange={this.handleChange} />
<label htmlFor="title">Contact Number</label>
</div>
{ this.state.progress==100? <div class="col-md-4">
<img class="responsive-img" src={this.state.avatarURL}></img>
</div>:""}
<br/>
<label style={{backgroundColor: 'steelblue', color: 'white', padding: 10, borderRadius: 4, pointer: 'cursor'}}>
Upload a photo
{/* {this.state.isUploading && <p>Progress: {this.state.progress}</p>}
{this.state.avatarURL && <img src={this.state.avatarURL} />} */}
<FileUploader
hidden
accept="image/*"
storageRef={firebase.storage().ref('images')}
onUploadStart={this.handleUploadStart}
onUploadError={this.handleUploadError}
onUploadSuccess={this.handleUploadSuccess}
onProgress={this.handleProgress}
/>
</label>
<div className="input-field">
<button className="btn pink lighten-1">Create</button>
</div>
</form>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
auth: state.firebase.auth
}
}
const mapDispatchToProps = dispatch => {
return {
createAd: (ad) => dispatch(createAd(ad))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CreateAd)
These are my codes.
You should use return window.location.replace("/") instead of return <Redirect to="/" />.
"React Router Redirect" redirects from A to B, for example <Redirect from="/about" to="/" />
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).