I am using Redux with ReactJS. I also am utilizing devise-JWT for auth. Upon a refresh of the page, my state is changed and loggedIn becomes "false". I also get 400 Bad Request error upon posting data through fetch. I can login just fine and be redirected.
My user reducer:
import {
SIGNUP_USER,
LOGIN_USER,
LOGOUT_USER,
STORE_TOKEN
} from '../actions/types'
const INITIAL_STATE = {
loggedIn: false,
currentUser: {}
}
export default (state = INITIAL_STATE, action) => {
switch(action.type){
case SIGNUP_USER:
return {
...state,
loggedIn: true,
currentUser: action.payload
}
case LOGIN_USER:
return {
...state,
loggedIn: true,
currentUser: action.payload
}
case LOGOUT_USER:
return {
...state,
user: state.users.filter(user => user.id !== action.payload.id),
loggedIn: false
}
case STORE_TOKEN:
return {
token: action.payload.token,
}
default:
return state
}
}
My action:
export function loginUser(data){
return (dispatch) => {
fetch("http://localhost:3000/login", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user: {
...data
}
})
})
.then(resp => {
//debugger
if (resp.ok) {
resp.json().then(json => {
localStorage.setItem('token', json.token)
dispatch({ type: LOGIN_USER, payload: json })
})
}
})
}
}
My login component:
import React from 'react'
import TextField from '#material-ui/core/TextField'
import Button from '#material-ui/core/Button'
import { loginUser } from '../actions/index'
import { connect } from 'react-redux'
class Login extends React.Component {
state = {
email: '',
password: ''
}
handleSubmit = (e) =>{
e.preventDefault()
let credentials = this.state
this.props.loginUser(credentials)
this.props.history.push('/bookmarks')
}
handleChange = (e) => {
this.setState({[e.target.name]: e.target.value})
}
render() {
const { email, password } = this.state
return (
<div className="login-form">
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
<div>
<TextField type="text" name="email" placeholder="Email" onChange={this.handleChange} value={email} />
</div>
<div>
<TextField type="password" name="password" placeholder="Password" onChange={this.handleChange} value={password}/>
</div><br></br>
<Button type="submit" value="Login">Login</Button>
</form>
</div>
)
}
}
const mapDispatch = (dispatch) => {
return {
loginUser: (credentials) => dispatch(loginUser(credentials))
}
}
export default connect(null, mapDispatch)(Login)
I used devise-JWT for authentication.
You're logged out because the only place you store the credentials is in the redux store, which is (essentially) a variable.
When you refresh the page, you reinitialise the store to the default state.
You haven't stored the credentials anywhere where they would persist, such as local storage or a cookie.
Related
I have set everything in Redux side and I see every action in Redux Devtool. It works all perfect. The problem occurs when I want to dispatch action in React Component. In login component I want to dispatch action, wait its response then depending on response redirect it to a page or show errors. Here are my codes:
userActions.js
import axios from "axios";
import {
LOGIN_USER,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAILED,
} from "./types";
const loginUserRequest = () => {
return {
type: LOGIN_USER,
};
};
const loginUserSuccess = (user) => {
return {
type: LOGIN_USER_SUCCESS,
payload: user,
};
};
const loginUserFailed = (error) => {
return {
type: LOGIN_USER_FAILED,
payload: error,
};
};
export const loginUser = (dataSubmitted) => {
return (dispatch) => {
dispatch(loginUserRequest());
axios
.post("/api/users/login", dataSubmitted)
.then((response) => {
dispatch(loginUserSuccess(response.data));
})
.catch((err) => {
dispatch(loginUserFailed(err));
});
};
};
userReducer.js:
import {
LOGIN_USER,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAILED,
} from "../actions/types";
const initialState = {
loading: false,
user: "",
error: "",
};
export default function (state = initialState, action) {
switch (action.type) {
case LOGIN_USER:
return { ...state, loading: true };
case LOGIN_USER_SUCCESS:
return { ...state, loading: false, user: action.payload, error: "" };
case LOGIN_USER_FAILED:
return { ...state, loading: false, user: "", error: action.payload };
default:
return state;
}
}
The above codes works great and does the job. The problem is in following code where I am dispatching the async action. After I run the code I get this.props.userData as undefined.
Login.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { loginUser } from "../../actions/userActions";
export class Login extends Component {
state = {
email: "",
password: "",
errors: [],
};
displayErrors = (errors) =>
errors.map((error, i) => (
<div className="alert alert-danger" key={i}>
{error}
</div>
));
handleSubmit = (e) => {
e.preventDefault();
var { email, password } = this.state;
var errors = [];
if (email === "") {
errors.push("Email is required");
}
if (password === "") {
errors.push("Password is required");
}
this.setState({ errors: errors });
//Problem occurs here
this.props.dispatch(loginUser({ email, password }));
if (response.payload.success) {
sessionStorage.setItem("jwt", response.payload.token);
sessionStorage.setItem("userId", response.payload._id);
this.props.history.push("/");
} else {
errors.push("Username and/or Password is not correct");
this.setState({ errors: errors });
}
};
render() {
return (
<form className="form-signin" onSubmit={this.handleSubmit}>
<h1 className="h3 mb-3 font-weight-normal">Sign in</h1>
{this.state.errors.length > 0 && this.displayErrors(this.state.errors)}
<label for="inputEmail" className="sr-only">
Email address
</label>
<input
type="email"
id="inputEmail"
className="form-control"
placeholder="Email address"
value={this.state.email}
onChange={(e) => {
this.setState({ email: e.target.value });
}}
required
autoFocus
/>
<label for="inputPassword" className="sr-only">
Password
</label>
<input
type="password"
id="inputPassword"
className="form-control"
placeholder="Password"
value={this.state.password}
onChange={(e) => {
this.setState({ password: e.target.value });
}}
required
/>
<div className="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me" /> Remember me
</label>
</div>
<button
className="btn btn-lg btn-primary btn-block"
type="submit"
onClick={this.handleSubmit}
>
Sign in
</button>
<Link to="/register">Sign Up</Link>
</form>
);
}
}
function mapStateToProps(state) {
return {
userData: state.user,
};
}
export default connect(mapStateToProps)(Login);
You need to install connected-react-router to manipulate with a history inside redux:
import { push } from 'connected-react-router';
export const loginUser = (dataSubmitted) => {
return (dispatch) => {
dispatch(loginUserRequest());
axios
.post("/api/users/login", dataSubmitted)
.then((response) => {
dispatch(loginUserSuccess(response.data));
if (response.payload.success) {
sessionStorage.setItem("jwt", response.payload.token);
sessionStorage.setItem("userId", response.payload._id);
push("/");
} else {
errors.push("Username and/or Password is not correct");
}
})
.catch((err) => {
dispatch(loginUserFailed(err));
});
};
};
I am trying to make user login page with react and redux.
//loginAction.js
export function loginRequest(user){
return{
type: types.LOGIN_REQUEST,
user
}
}
export function loginSuccess(user){
return{
type: types.LOGIN_SUCCESS,
user
}
}
export function loginFailure(err){
return{
type: types.LOGIN_FAILURE,
err
}
}
export function login(username, password){
return function(dispatch) {
dispatch(loginRequest({ username }));
userApi.userLogin(username, password)
.then(
user => {
dispatch(loginSuccess(user));
},
err => {
dispatch(loginFailure(err.toString()))
}
)
}
}
//userReducer.js
const login = (state=initialState, action) => {
switch(action.types){
case types.LOGIN_SUCCESS:
case types.LOGIN_REQUEST:
return {
loggedIn: true,
user: action.user
}
case types.LOGIN_FAILURE:
return {}
default:
return state
}
};
LoginPage.js
import React, { useState, useEffect} from 'react';
import PropTypes from 'prop-types';
import {useDispatch, useSelector} from 'react-redux';
import {Redirect} from 'react-router-dom';
import {login} from '../redux/actions/loginAction'
const LoginPage = () => {
const [inputs, setInputs] = useState({
username: '',
password: ''
})
const [isauthenticated, setIsAuthenticated] = useState(false);
const { username, password } = inputs
const dispatch = useDispatch();
const loggingIn = useSelector(state => state.user);
const handleChange = (e) => {
const {name, value} = e.target;
setInputs(inputs =>({...inputs, [name]: value}))
}
const handleSubmit = (event) => {
event.preventDefault();
// setIsAuthenticated(true)
// this line needs to be improved
if( username && password ){
dispatch(login(username, password))
}
}
return(
<div className='field col-xs-5 col-lg-3'>
{
isAuthenticated ?
<Redirect to='/uploadfile' /> :
(
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleChange}
name="username"
label="username"
className="form-control"
placeholder="ID"
value={username}
/>
<input
type="password"
onChange={handleChange}
name="password"
label="password"
className="form-control"
placeholder="Password"
/>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
)
}
</div>
)
}
export default LoginPage;
Inside of LoginPage component, I have isAuthenticated state that I want to set true when its action returns 'LOGIN_SUCCESS', not always set true.
Is there a way to check it, like this?
const handleSubmit = (event) => {
event.preventDefault();
// setIsAuthenticated(true)
if( username && password ){
// can I do?
if(dispatch(login(username, password)) === 'types.LOGIN_SUCCESS'){
setIsAuthticated(true)
}
}
}
const login = (state=initialState, action) => {
switch(action.types){
case types.LOGIN_REQUEST:
return {
loggedIn: false,
user: null
}
case types.LOGIN_SUCCESS:
return {
loggedIn: true,
user: action.user
}
case types.LOGIN_FAILURE:
return {
loggedIn: false,
user: null
}
default:
return state
}
};
I would write it like that, you're not logged in before login_success. And inside loginform you should only check props - loggedIn and user.
I'm using Axios to perform a GET request and then store the information into the {information} property, and then I store all of that in my state. How can I use setState to set the state of the input fields after the compoenntDidMount()?? I've tried a .then() after the this.props.getItemCredential, but it doesn't work because it's not a standard fetch request?
I've also tried to set the state of the input value like: {this.state.profile.credential.itemArray[0].company} and it returns undefined even though I see it in my redux chrome extension as properly being in the state.
I think the issue is that it's returning undefined cause it cannot change the value of the input until the getItemCredential returns, but then how do I wait for it to finish and then adjust the: this.state.(whatever) for each one?
What my component looks like:
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import TextFieldGroup from '../../common/TextFieldGroup';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getItemCredential, getCurrentProfile } from '../../../actions/profileActions';
import Spinner from '../../common/Spinner';
class EditInfo extends Component {
constructor(props) {
super(props);
this.state = {
company: '',
title: '',
location: '',
errors: {},
disabled: false
}
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
componentDidMount() {
this.props.getItemCredential(this.props.match.params.id)
this.props.getCurrentProfile()
}
onChange = (e) => {
this.setState({[e.target.name]: e.target.value});
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({errors: nextProps.errors});
}
}
onSubmit = (e) => {
e.preventDefault();
const itemData = {
company: this.state.company,
title: this.state.title,
location: this.state.location,
}
}
render() {
const { errors } = this.state;
const { profile, loading } = this.props.profile;
const { credential } = this.props.profile;
let editContent;
if (profile === null || loading) {
editContent = <Spinner />
} else {
editContent = (
<React.Fragment>
<div>
<form onSubmit={this.onSubmit}>
<TextFieldGroup
placeholder={this.state.company}
name="company"
value={this.state.company}
onChange={this.onChange}
error={errors.company}
required
/>
<TextFieldGroup
placeholder="* Job Title"
name="title"
value={this.state.title}
onChange={this.onChange}
error={errors.title}
required
/>
<TextFieldGroup
placeholder="Location"
name="location"
value={this.state.location}
onChange={this.onChange}
error={errors.location}
required
/>
<div class="col-xl-4 col-lg-4 col-md-4 col-sm-12 text-center my-auto">
<input type="submit" value="Submit" class="button text-center m-auto add-credentials-button mt-4" />
</div>
</form>
</div>
);
}
return (
<div>
{editContent}
</div>
)
}
}
EditInfo.propTypes = {
profile: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
getItemCredential: PropTypes.func.isRequired,
getCurrentProfile: PropTypes.func.isRequired,
credential: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
profile: state.profile,
credential: state.credential,
errors: state.errors
});
export default connect(mapStateToProps, { addExperience, getExperienceCredential, getCurrentProfile })(withRouter(EditExperience));
Here is what my state markup looks like:
profile: {
credential: {
itemArray: [
0: {
title: 'Some entry',
company: 'Some entry',
Location: 'some entry'
}
]
}
}
Here is the axios request I'm making:
export const getItemCredential = (id) => dispatch => {
dispatch(setProfileLoading());
axios.get(`/profile/item-credential/${id}`)
.then(res =>
dispatch({
type: GET_ITEM_CREDENTIAL,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Here is what my reducer looks like:
import { GET_PROFILE, PROFILE_LOADING, GET_ITEM_CREDENTIAL } from '../actions/types';
const initialState = {
profile: null,
credential: null,
loading: false
}
export default function(state = initialState, action) {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case GET_ITEM_CREDENTIAL:
return {
...state,
credential: action.payload
}
default:
return state;
}
}
You can do a .then if you change getItemCredential so it returns a Promise:
export const getItemCredential = (id) => dispatch => {
dispatch(setProfileLoading());
return axios.get(`/profile/item-credential/${id}`)
.then(res => {
dispatch({
type: GET_ITEM_CREDENTIAL,
payload: res.data
});
return res.data;
})
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
throw err;
});
}
Notice the added return in getItemCredential. Also, notice that res.data is returned from the Promise so that it can be accessed by your component that awaits it. I also re-threw the caught error so that the Promise continues in the rejected state instead of resolving to undefined.
I was driven crazy by my first react-redux app. My Redux State is never updated.
I tried to find every solution on the website but they are not helping. One very similar question here used promise but I am not.
Redux-dev-tools catch the actions about the login_success but the global state is never updated, please tell me what should I do to debug if you could, thank you so much.
First Index.js
import ... from ...
const compostEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers({
auth: authReducer,
})
const store = createStore(rootReducer, compostEnhancer(
applyMiddleware(thunk)
));
const app = (<BrowserRouter><App /></BrowserRouter>)
ReactDOM.render(<Provider store={store}>{app}</Provider>, document.getElementById('root'));
registerServiceWorker();
authReducer.js :
import * as actionTypes from '../actions/actionTypes';
const initialState = {
token: null,
userId: null,
error: null,
loading: false
}
const authReducer = (state = initialState, action) => {
switch (action.types) {
case actionTypes.LOGIN_START:
return {
...state,
};
case actionTypes.LOGIN_SUCCESS:
return {
...state,
token: action.idToken,
userId: action.userId,
loading: false,
error:null
}
default:
return state;
}
}
export default authReducer;
authAction.js:
import * as actionTypes from './actionTypes';
import axios from 'axios';
export const loginStart= () =>{
return {
type: actionTypes.LOGIN_START
};
}
export const loginSuccess = (token,userId) =>{
return {
type: actionTypes.LOGIN_SUCCESS,
userId: userId,
idtoken: token,
}
}
export const loginFail = (error) =>{
return {
type:actionTypes.LOGIN_FAIL,
error:error
}
}
export const auth = (email,password,isSignup ) =>{
return dispatch =>{
dispatch(loginStart());
const authData = {
email: email,
password: password,
returnSecureToken:true
}
let url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=...'
if(!isSignup ){
url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=...'
}
axios.post(url, authData)
.then( response =>{
console.log(response);
dispatch(loginSuccess(response.data.idToken, response.data.localId))
})
.catch(err=>{
console.log(err);
dispatch(loginFail(err));
})
}
}
Login.js (Component):
import ... from ...;
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
isSignup: false,
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = e => {
e.preventDefault();
// This will trigger actions
this.props.onAuth(this.state.email, this.state.password, this.state.isSignup);
console.log(this.props.token) //here I can Get Token
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
render() {
let form =
<div className={classes.ContactData} >
<h4>Sign IN</h4>
<form onSubmit={this.handleSubmit} >
<Input
elementType='input'
name="email"
required
label="Email"
placeholder="Email"
value={this.state.email}
margin="normal"
onChange={this.handleChange}
/>
<br />
<Input
elementType='input'
required
name="password"
value={this.state.password}
label="Password"
onChange={this.handleChange}
/>
<br />
<Button
color="primary"
type="submit"
>Submit</Button>
<Button color="primary" href="/"> CANCLE</Button>
</form>
</div>
if (this.props.loading) {
form = <Spinner />
}
let errorMessage = null;
if (this.props.error) {
errorMessage =(
<p>{this.props.error.message} </p>
)
}
let token = null;
if(this.props.token){
token = this.props.token.toString()
}
return (
<div>
{errorMessage}
{ form }
</div>
)
}
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
token:state.auth.idToken,
}
}
const mapDispatchToProps = dispatch => {
return {
onAuth: (email, password, isSignup) => dispatch(actions.auth(email, password, isSignup)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
And also, the problem leads to my Spinner and show ErrorMessage not working.
I suppose its a typo.
instead of
switch (action.types)
try this
switch (action.type)
In reducer, we get an object returned from the actions, on the argument action.
I'm developing a mobile application by use react-native and redux,thunk and it's the first time I write by react-native.
My problem is I call an api and the response is valid, I want to do somethings as update UI, navigate to new screen... for do that I will need to used flag in my component to mark it.
This is login example, after user login success, i want to navigate to Home screen. for do that, i need check an flag isLoginSuccess in props on the method componentWillReceiveProps to know user have been login success or not, but i think it's not good solution.
My question is we have other way to do it without use flag.
action.js
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGIN = "LOGIN";
export function login(username, password) {
console.log(username)
return {
type: LOGIN,
username: username,
password: password
}
}
export function loginSuccess(data) {
return {
type: LOGIN_SUCCESS,
loginData: data
}
}
export function loginFail(error) {
return {
type: LOGIN_FAIL,
error: error
}
}
export function doLogin(username, password) {
return (dispatch) => {
dispatch(login(username, password))
api.login(username, password)
.then(response => response.json())
.then(jsonData => {
console.log(jsonData)
dispatch(loginSuccess(jsonData))
})
.catch((error) => {
dispatch(loginFail(error))
})
}
}
reducer.js
const initialState = {
loginData:{},
isLoginDoing : false,
isLoginSuccess : false,
username :"",
password : "",
error : {},
}
export default function(state = initialState , action ={}){
switch(action.type){
case actionType.LOGIN:{
return {
...state,
username: action.username,
password: action.password,
isLoginDoing : true
}
}
case actionType.LOGIN_SUCCESS:{
return {
...state,
loginData: action.loginData,
isLoginDoing : false,
isLoginSuccess : true
}
}
case actionType.LOGIN_FAIL:{
return {
...state,
isLoginDoing : false,
isLoginSuccess : false,
error : action.error
}
}
default :{
return state
}
}
}
component.js
import { connect } from "react-redux"
import { bindActionCreators } from 'redux';
import { doLogin } from '../actions'
import BaseComponent from './baseComponent'
class Login extends BaseComponent {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
}
this.functionLogin = this.functionLogin.bind(this);
}
functionLogin() {
const { username, password } = this.state;
if(!this.props.loginReducer.isLoginDoing){
this.props.doLogin(username, password)
}
}
componentWillReceiveProps (nextProps) {
console.log("componentWillReceiveProps");
const { navigate, goBack } = this.props.navigation;
if(nextProps.loginReducer.isLoginSuccess){
// this._navigateTo('Home')
navigate('Home',nextProps.loginReducer.loginData);
}
}
render() {
const { navigate, goBack } = this.props.navigation;
return (
<View style={{ backgroundColor: 'color', marginTop: 10 }} >
<TextInput
style={{ height: 40 }}
placeholder="Username"
onChangeText={value => this.setState({ username: value })}
/>
<TextInput
style={{ height: 40 }}
placeholder="Password"
onChangeText={value => this.setState({ password: value })}
/>
<Button
onPress={this.functionLogin}
title="Login"
color="#841584"
/>
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
loginReducer: state.loginReducer
};
}
function mapDispatchToProps(dispatch) {
return {
doLogin: (username, password) => dispatch(doLogin(username, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
Thanks
In your function doLogin, you can dispatch a navigation action after dispatch(loginSuccess(jsonData)).
For example for react-navigation (if you have integrated it with redux, if it's not the case, see https://reactnavigation.org/docs/guides/redux):
dispatch(NavigationActions.navigate({routeName: 'Home'});
(Don't forget import { NavigationActions } from 'react-navigation';)