I have a problem with dispatch event in redux - javascript

On the first click the data array received from dispatch is empty, however if i click on the save button one more time it works perfectly fine.
Here what i get from the console from Account.js
As you can see i get the error false in data with user information which is exactly what i need.
I'm ensure what is wrong here and why it does not work on the first click.
Account.js
import React, { Component, useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
// react-bootstrap components
import {
Button,
Card,
Form,
Row,
Col,
} from "react-bootstrap";
import userActions from '../../redux/auth/actions';
const {
updateAccount,
} = userActions;
function Account() {
const loggedUser = JSON.parse(localStorage.getItem('user'));
const dispatch = useDispatch();
const history = useHistory();
const user = useSelector(
state => state.Auth
);
const [first_name, setFirstName] = useState(loggedUser.first_name);
const [last_name, setLastName] = useState(loggedUser.last_name);
const [email, setEmail] = useState(loggedUser.email);
const [password, setPassword] = useState('');
const [success, setSuccess] = useState(false);
const handleUpdateAccount = e => {
e.preventDefault();
setSuccess(false);
const obj = {first_name: first_name, last_name: last_name, email: email, password: password};
dispatch(updateAccount(obj));
console.log(user);
if(user.data) {
if(user.data.error == false) {
setSuccess(true);
}
}
};
return (
<>
<Row>
<Col offset-md="2" md="8">
<Card>
<Card.Header>
<Card.Title as="h4">Mon Compte</Card.Title>
</Card.Header>
<Card.Body>
<Form onSubmit={handleUpdateAccount}>
<Row>
<Col className="pl-1" md="4">
<Form.Group>
<label htmlFor="exampleInputEmail1">
Adresse e-mail
</label>
<Form.Control
value={email}
onChange={e => setEmail(e.target.value)}
type="email"
></Form.Control>
</Form.Group>
</Col>
<Col className="pr-1" md="5">
<Form.Group>
<label>Mot de passe</label>
<Form.Control
value={password}
onChange={e => setPassword(e.target.value)}
type="password"
></Form.Control>
</Form.Group>
</Col>
</Row>
<Row>
<Col className="pr-1" md="6">
<Form.Group>
<label>Prénom</label>
<Form.Control
value={first_name}
onChange={e => setFirstName(e.target.value)}
type="text"
></Form.Control>
</Form.Group>
</Col>
<Col className="pl-1" md="6">
<Form.Group>
<label>Nom de famille</label>
<Form.Control
value={last_name}
onChange={e => setLastName(e.target.value)}
type="text"
></Form.Control>
</Form.Group>
</Col>
</Row>
{(user.data && user.data.error) && <div className="error-message mb-2">{user.data.message}</div>}
{(success) && <div className="success-message mb-2">Succès</div>}
<Button
className="btn-fill pull-right mt-3"
type="submit"
variant="info"
disabled={user.isLoading}
>
{user.isLoading && <span>Mettre à jour...</span>}
{!user.isLoading && <span>Mettre à jour</span>}
</Button>
<div className="clearfix"></div>
</Form>
</Card.Body>
</Card>
</Col>
</Row>
</>
);
}
export default Account;
saga.js
import { all, takeEvery, put, call } from 'redux-saga/effects';
import actions from './actions';
import axios from "axios";
function update_account(data){
var actionUrl = '/pages/update_account';
const isLoggedIn = JSON.parse(localStorage.getItem('user'));
return axios ({
method: 'POST',
url: process.env.REACT_APP_API_URL + actionUrl,
data: {
data,
user_id: isLoggedIn.id
},
headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" }
});
}
function* updateAccount(payload) {
try {
const resp = yield call(update_account, payload.payload.data);
if(resp.data.json.error == false) {
if(resp.data.json.user) {
// Do something
localStorage.setItem("user", JSON.stringify(resp.data.json.user));
}
}
yield put(actions.postToApiSuccess(resp.data.json));
} catch (error) {
yield put(actions.postToApiError(error));
}
}
export default function* rootSaga() {
yield all([
takeEvery(actions.UPDATE_ACCOUNT, updateAccount),
]);
}
reducer.js
import actions from './actions';
const user = JSON.parse(localStorage.getItem("user"));
const initState = {
isLoading: false,
errorMessage: false,
data: [],
loggedUser: user
};
export default function reducer(
state = initState,
{ type, payload }
) {
switch (type) {
case actions.UPDATE_ACCOUNT:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.UPDATE_COMPANY_ACCOUNT:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.POST_TO_API:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.POST_TO_API_SUCCESS:
return {
...state,
isLoading: false,
data: payload.data,
errorMessage: false,
loggedUser: payload.data.user
};
case actions.LOGOUT_USER:
return {
...state,
loggedUser: null,
data: []
};
case actions.POST_TO_API_ERROR:
return {
...state,
isLoading: false,
errorMessage: 'There is a problem'
};
default:
return state;
}
}
actions.js
const actions = {
LOGOUT_USER: 'LOGOUT_USER',
POST_TO_API: 'POST_TO_API',
UPDATE_ACCOUNT: 'UPDATE_ACCOUNT',
UPDATE_COMPANY_ACCOUNT: 'UPDATE_COMPANY_ACCOUNT',
POST_TO_API_SUCCESS: 'POST_TO_API_SUCCESS',
POST_TO_API_ERROR: 'POST_TO_API_ERROR',
postToApi: data => {
return {
type: actions.POST_TO_API,
payload: { data },
};
},
updateAccount: data => {
return {
type: actions.UPDATE_ACCOUNT,
payload: { data },
};
},
updateCompanyAccount: data => {
return {
type: actions.UPDATE_COMPANY_ACCOUNT,
payload: { data },
};
},
logoutUser: data => {
return {
type: actions.LOGOUT_USER
};
},
postToApiSuccess: data => ({
type: actions.POST_TO_API_SUCCESS,
payload: { data },
}),
postToApiError: error => ({
type: actions.POST_TO_API_ERROR,
payload: { error },
}),
};
export default actions;

In your initialState "data" is an empty array and you are only updating it when actions.POST_TO_API_SUCCESS get trigger.

Related

In login or signup form, after I click on login or signup, I must reload before it detects a token in localStorage and then it redirects

I am using redux to keep track of the user's state and handle any actions to be performed via backend. Once a user enters their information, it dispatches the login action creator with the user data and history for redirecting.
Login.js (Signup.js is similar as well)
import React, { Fragment, useEffect, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import {
Container,
Typography,
Button,
TextField,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
CircularProgress
} from '#material-ui/core';
import { useFormik } from 'formik';
import * as yup from 'yup';
import { PASSWORD_REGEX, EMAIL_REGEX } from "../../validators/form-validation";
import useStyles from "./styles";
import { useDispatch, useSelector } from 'react-redux';
import { login } from '../../redux';
// validation schema
const validationSchema = yup.object({
email: yup
.string('Enter your email.')
.email('Enter a valid email.')
.matches(EMAIL_REGEX, "Invalid email.")
.required('Email is required.'),
password: yup
.string('Enter your password')
.min(6, 'Password should be of minimum 6 characters length.')
.matches(PASSWORD_REGEX, "Invalid password. Must be alphanumeric.")
.required('Password is required.'),
});
function Login() {
const classes = useStyles();
const dispatch = useDispatch();
const history = useHistory();
const [errorModal, setErrorModal] = useState(false);
const state = useSelector(state => state.users);
const openModal = () => {
setErrorModal(true);
};
const closeModal = () => {
setErrorModal(false);
};
const formik = useFormik({
initialValues: {
email: '',
password: ''
},
validationSchema: validationSchema,
onSubmit: (userData) => {
dispatch(login(userData, history));
}
});
if (state.loading) {
return <CircularProgress />
}
return (
<Fragment>
<Container className={classes.container} maxWidth="sm">
<Typography
variant="h4"
className={classes.header}
>
LOGIN
</Typography>
<form
className={classes.form}
onSubmit={formik.handleSubmit}
>
<Typography
className={classes.label}
variant="body2"
>
Email
</Typography>
<TextField
name="email"
type="email"
className={classes.textbox}
variant="outlined"
label="Enter Email"
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && !!(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
fullWidth
/>
<Typography
className={classes.label}
variant="body2"
>
Password
</Typography>
<TextField
className={classes.textbox}
name="password"
type="password"
variant="outlined"
label="Enter Password"
fullWidth
value={formik.values.password}
onChange={formik.handleChange}
error={formik.touched.password && !!(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
/>
<Button
className={classes.buttonSubmit}
variant="contained"
color="primary"
type="submit"
size="large"
fullWidth
>
LOGIN
</Button>
<Typography
variant="subtitle2"
>
{"Don't have an account? Click "}
<Link
to="/signup"
>
here
</Link>
{" to sign up!"}
</Typography>
</form>
</Container>
<Dialog
open={errorModal}
onClose={closeModal}
>
<DialogTitle>
Login Error
</DialogTitle>
<DialogContent>
<DialogContentText>
{state.error}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={closeModal} color="primary">
OKAY
</Button>
</DialogActions>
</Dialog>
</Fragment>
)
}
export default Login;
login action
import {
LOGIN_SUCCESS,
LOGIN_REQUEST,
LOGIN_FAILURE,
SIGNUP_REQUEST,
SIGNUP_SUCCESS,
SIGNUP_FAILURE,
LOGOUT,
} from "./usersConstants";
import * as api from "../../api/users";
export const login = (user, history) => {
return async (dispatch) => {
try {
dispatch({
type: LOGIN_REQUEST,
});
const { data } = await api.login(user);
dispatch({
type: LOGIN_SUCCESS,
payload: data.user
});
history.push("/home");
} catch (error) {
dispatch({
type: LOGIN_FAILURE,
payload: error.message
});
}
};
};
usersReducer.js
import {
LOGIN_SUCCESS,
LOGIN_REQUEST,
LOGIN_FAILURE,
SIGNUP_REQUEST,
SIGNUP_SUCCESS,
SIGNUP_FAILURE,
LOGOUT
} from "./usersConstants";
const INITIAL_STATE = {
loading: false,
error: '',
token: null,
userId: null
};
export const usersReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case LOGIN_REQUEST: {
return {
...state,
loading: true
}
}
case LOGIN_SUCCESS: {
localStorage.setItem('userData', JSON.stringify(
{
userId: action.payload.userId,
token: action.payload.token,
}
));
return {
...state,
loading: false,
token: action.payload.token,
userId: action.payload.userId,
error: ''
}
}
case LOGIN_FAILURE: {
return {
...state,
loading: false,
token: null,
userId: null,
error: action.payload
}
}
case SIGNUP_REQUEST: {
return {
...state,
loading: true,
}
}
case SIGNUP_SUCCESS: {
localStorage.setItem('userData', JSON.stringify(
{
userId: action.payload.userId,
token: action.payload.token,
}
));
return {
...state,
loading: false,
token: action.payload.token,
userId: action.payload.userId,
error: ''
}
}
case SIGNUP_FAILURE: {
return {
...state,
loading: false,
token: null,
userId: null,
error: action.payload
}
}
case LOGOUT: {
localStorage.removeItem("userData");
return {
...state,
loading: false,
token: null,
userId: null,
error: ''
}
}
default: return state;
}
};
If there dispatches LOGIN_SUCCESS, then I store the token received into localStorage. I have double checked and the data is reflected there as well. As well as in the logs, it does show LOGIN_REQUEST followed as LOGIN_SUCCESS and the payload is reflected as well.
In my App.js, I have predefined routes for if a user is logged in or not, checked via a token in the localStorage. However, it seems that the state isn't reflected right away (not checking the latest state of users) and thus can't login after the login button is clicked, but instead I have to reload. Same thing goes for when an error comes back from the backend and I want to display it, I still have to click the login button again for it to do so. Any suggestions? Thank you.

How to update state with redux

I'm trying to build simple login form (data for authorization from API). So I have Slice for auth here is code :
auth.js
import { createSlice } from '#reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
initialState: {
isLoggedIn: false,
token: null,
role: null
},
reducers: {
logIn: (state) => {
state.isLoggedIn = true
},
role:(state, action) =>{
state.role = action.payload
},
token:(state, action) =>{
state.token = action.payload
},
logOut: (state) => {
state.isLoggedIn = false
},
},
})
export default authSlice.reducer;
export const { logIn, logOut, role, token } = authSlice.actions
authService.js :
import axios from 'axios';
import { api } from '../api/index'
export function authenticateUser(username, password) {
axios
.post(api + 'login', {username, password})
.then(res => {
console.log(res.headers.authorization)
})
}
LoginForm.js
import React, { Component } from 'react';
import { Form, Col, Button } from 'react-bootstrap';
import { IoMdLogIn } from "react-icons/all";
import { authenticateUser } from '../services/authService'
export default class LoginForm extends Component{
constructor(props) {
super(props);
this.state = this.initialState;
this.credentialsChange = this.credentialsChange.bind(this);
this.userLogin= this.userLogin.bind(this);
}
initialState = {
username: '', password: ''
}
userLogin = (e) => {
e.preventDefault();
authenticateUser(this.state.username, this.state.password);
this.setState( () => this.initialState)
}
credentialsChange = e => {
this.setState({
[e.target.name]:e.target.value
});
}
render(){
const {username, password} = this.state;
return(
<Form onSubmit={this.userLogin} id="loginFormId">
<Form.Row>
<Form.Group as={Col} controlId="formGridCountry">
<Form.Label>Username</Form.Label>
<Form.Control required autoComplete="off"
type="text" name="username"
value={username}
onChange={this.credentialsChange}
className={"bg-light"}
placeholder="Username" />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId="formGridZipCode">
<Form.Label>Password</Form.Label>
<Form.Control required autoComplete="off"
type="password" name="password"
value={password}
onChange={this.credentialsChange}
className={"bg-light"}
placeholder="Password" />
</Form.Group>
</Form.Row>
<Button type="submit" variant="success">
<IoMdLogIn />
</Button>
</Form>
);
}
}
What I'm trying to reach is : I want to update state isLoggedIn : true after calling function authenticateUser.
I've tried to use const dispatch = useDispatch() and then calling dispatch(logIn()) but it's throwing error.
Where should I call dispatcher to update state?
You need to call the dispatcher in your AuthService.js in the api response.
Check the response, if it is ok, store it. If your redux is well implemented, it will work.
axios
.post(api + 'login', {username, password})
.then(res => {
console.log(res.headers.authorization)
//Call it here, or create a function and call it here.
})
}
If it doesn't work, please share the error with us

I keep getting Can't perform a React state update on an unmounted component

I keep getting this error for two of my React components and i can't figure it out. I just started to learn to use hooks and I can't seem to fix this error message.
I tried searching online and going through a few suggested methods but it doesn't seem to work for me.
I saw an article saying to add this code below but it has no effect at all.
let mounted = true
return function cleanup() {
mounted = false
}
Here's the two errors:
"index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in BoardAdmin (at App.js:125)
in component (at PrivateRoute.js:9)"
"Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SFTPList (at CsvExtractor.js:206)
in div (created by Col)
in Col (at CsvExtractor.js:205)
in div (created by Row)
in Row (at CsvExtractor.js:183)
in form (created by Form)
in Form (at CsvExtractor.js:129)
in CsvExtractor (at BoardAdmin.js:30)
in header (at BoardAdmin.js:29)
in div (at BoardAdmin.js:28)
in BoardAdmin (at App.js:125)
in component (at PrivateRoute.js:9)"
csvextractor.js
import React, { useState, useEffect, useRef } from 'react';
import Dropzone from 'react-dropzone';
import MultiSelect from 'react-multiple-select-dropdown-lite';
import 'react-multiple-select-dropdown-lite/dist/index.css';
import 'react-datepicker/dist/react-datepicker.css';
import DatePicker from 'react-datepicker';
import SFTPList from './SFTPDownload';
import { Form, Row, Col, Button } from 'react-bootstrap';
import FileService from '../services/file.service';
import { history } from '../helpers/history';
const CsvExtractor = () => {
const [file, setFile] = useState(null); // state for storing actual file
const [details, setDetails] = useState({
title: '',
description: '',
});
const [errorMsg, setErrorMsg] = useState('');
const dropRef = useRef(); // React ref for managing the hover state of droppable area
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(null);
const [filters, setFilters] = useState([]);
const filterOptions = [
{
value: 'translate',
label: 'Translate to EN',
},
{
value: 'emailentrant',
label: 'Email Entrant',
},
{
value: 'emailsortant',
label: 'Email Sortant',
},
{
value: 'appelentrant',
label: 'Appel Entrant',
},
{
value: 'appelsortant',
label: 'Appel Sortrant',
},
{
value: 'chat',
label: 'Chat',
},
];
const handleSelect = (selections) => {
setDetails({
...details,
description: `${selections}`,
});
setFilters(selections.split(','));
};
const handleInputChange = (event) => {
setDetails({
...details,
[event.target.name]: event.target.value,
});
};
const onDrop = (files) => {
const [uploadedFile] = files;
setFile(uploadedFile);
const fileReader = new FileReader();
fileReader.onload = () => {};
fileReader.readAsDataURL(uploadedFile);
dropRef.current.style.border = '2px dashed #e9ebeb';
};
const onChange = (dates) => {
const [start, end] = dates;
const tdate = end === null ? '' : end.toString().slice(4, 15);
setDetails({
...details,
title: `${start.toString().slice(4, 15)} - ${tdate}`,
});
setStartDate(start);
setEndDate(end);
};
const updateBorder = (dragState) => {
if (dragState === 'over') {
dropRef.current.style.border = '2px solid #000';
} else if (dragState === 'leave') {
dropRef.current.style.border = '2px dashed #e9ebeb';
}
};
const handleOnSubmit = async (event) => {
event.preventDefault();
try {
const { title, description } = details;
if (title.trim() !== '' && description.trim() !== '') {
if (file) {
const formData = new FormData();
formData.append('startDate', startDate);
formData.append('endDate', endDate);
formData.append('filters', filters);
formData.append('file', file);
formData.append('title', title);
formData.append('description', description);
setErrorMsg('');
await FileService.uploadFile(formData);
history.push('/list');
} else {
setErrorMsg('Please select a file to add.');
}
} else {
setErrorMsg('Please enter all the field values.');
}
} catch (error) {
error.response && setErrorMsg(error.response.data);
}
};
return (
<React.Fragment>
<Form className="search-form">
{errorMsg && <p className="errorMsg">{errorMsg}</p>}
<Row>
<Col>
<Form.Group controlId="title">
<Form.Control
type="text"
name="title"
value={details.title || ''}
placeholder="Enter title"
onChange={handleInputChange}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="description">
<Form.Control
type="text"
name="description"
value={details.description || ''}
placeholder="Enter description"
onChange={handleInputChange}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<div>
<h5>Select date range</h5>
</div>
<DatePicker
selected={startDate}
onChange={onChange}
startDate={startDate}
endDate={endDate}
selectsRange
inline
/>
</Col>
<Col>
<h5>Select filters (at least one)</h5>
<MultiSelect
style={{ backgroundColor: 'white', marginTop: '10px' }}
onChange={handleSelect}
options={filterOptions}
/>
</Col>
</Row>
<Row>
<Col xs={12}>
{/* <div className="upload-section"> */}
<Dropzone
onDrop={onDrop}
onDragEnter={() => updateBorder('over')}
onDragLeave={() => updateBorder('leave')}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps({ className: 'drop-zone' })} ref={dropRef}>
<input {...getInputProps()} />
<p>Drag and drop a file OR click here to select a file</p>
{file && (
<div>
<strong>Selected file:</strong> {file.name}
</div>
)}
</div>
)}
</Dropzone>
{/* </div> */}
</Col>
<Col>
<SFTPList />
</Col>
</Row>
<Button variant="primary" type="submit" onClick={handleOnSubmit}>
Submit
</Button>
</Form>
</React.Fragment>
);
};
export default CsvExtractor;
adminboard.js is below
import React, { useState, useEffect, useRef } from 'react';
import 'react-multiple-select-dropdown-lite/dist/index.css';
import 'react-datepicker/dist/react-datepicker.css';
import UserService from '../services/user.service';
import CsvExtractor from './CsvExtractor';
const BoardAdmin = () => {
const [content, setContent] = useState('');
useEffect(() => {
UserService.getAdminBoard().then(
(response) => {
setContent(response.data);
},
(error) => {
const _content =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
setContent(_content);
},
);
}, []);
return (
<div className="container">
<header className="jumbotron">
<CsvExtractor />
</header>
</div>
);
};
export default BoardAdmin;
If fixed it with the code below
const [filesList, setFilesList] = useState([]);
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
const source = axios.CancelToken.source();
const getFilesList = async () => {
try {
const { data } = await axios.get('api/file/getallfiles/', {
headers: authHeader(),
cancelToken: source.token,
});
setErrorMsg('');
setFilesList(data);
} catch (error) {
if (axios.isCancel(error)) {
} else {
error.response && setErrorMsg(error.response.data);
}
}
};
getFilesList();
return () => {
source.cancel();
};
}, []);

Redux state updates but component state not changing from first time

I am developing a login/register system using Redux,Hooks and Axios the action should fetch the backend and return a session to be updated in my reducer , after that I am trying to console.log(session) from my component the first time it is {} empty 'The initial state of session' is consoled the second time it is updated , I checked my redux state and everything works good and the state is updated from the first time so not a redux issue .The problem is in my component as I need it to wait for the Redux finishing and then console.log() , I tried to setTime() but it waits for the given time and after that It console the initial state {} again.
My code:
Component:
The problem is in the Redirect() function
import { LoginAction } from "../../Redux/Actions";
import { useForm } from "react-hook-form";
import { connect } from "react-redux";
const Login = (props) => {
let history = useHistory();
const alert = useAlert();
const { handleSubmit, register, errors } = useForm();
const onSubmit = (data) => {
const userData = {
username: data.username.toUpperCase(),
password: data.password,
};
props.login(userData);
setTimeout(1000, Redirect());
};
const Redirect = () => {
if (props.session.user) {
console.log(props.session);
sessionStorage.setItem("storedSession", props.session.user.username);
history.push("/dashboard");
} else {
alert.show(<div style={{ size: "10px" }}>{props.session.error}</div>);
}
};
return (
<div className="login">
<div className="login-form">
<h3 className="title">Sign In</h3>
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
placeholder="Enter your username"
name="username"
id="username"
ref={register({ required: true })}
/>
{errors.username && errors.username.type === "required" && (
<p className="error-before-submit">This is required</p>
)}
<input
id="password"
placeholder="Enter your password"
name="password"
type="password"
ref={register({ required: true })}
/>
{errors.password && errors.password.type === "required" && (
<p className="error-before-submit">This is required</p>
)}
<input
className="btn"
type="submit"
ref={register({ required: true })}
/>
<a href="/register" className="register-link">
Create an account
</a>
</form>
</div>
</div>
);
};
const mapStateToProps = (state) => ({
session: state.Session,
});
const mapDispatchToProps = (dispatch) => ({
login: (user) => dispatch(LoginAction(user)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Reducer:
const initialState = {
Session: {},
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case Register:
return {
...state,
Session: action.payload,
};
case Login:
return {
...state,
Session: action.payload,
};
default:
return state;
}
};
export default rootReducer;
Action:
export function LoginAction(user) {
return (dispatch) => {
Axios.post(
"/api/login",
{
username: user.username,
password: user.password,
},
{
headers: { "Access-Control-Allow-Origin": "*" },
withCredentials: true,
crossdomain: true,
}
).then((res) => {
dispatch({
type: Login,
payload: res.data,
});
});
};
}
How can I make my component take the updated state not the initial state FROM FIRST CLICK ??
You can leverage useEffect hook instead of using timeout
const {session} = props;
useEffect(()=>{
Redirect()
},[session])

React view only gets fresh data on page reload?

I'm building a simple campgrounds CRUD app to get some practice with the MERN stack and Redux.
Adding a campground is working fine. I'm routing to the campgrounds list page after adding a campground. But unless I reload the page, fresh data isn't retrieved.
I figured it has something to do with React's lifecycle methods.
Code:
manageCampground.js
import React, { Component } from 'react';
import TextField from '#material-ui/core/TextField';
import Card from '#material-ui/core/Card';
import Button from '#material-ui/core/Button';
import '../../styles/addCampground.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
} from './actions/campgroundActions';
class AddCampground extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
description: '',
cost: ''
};
}
componentDidMount() {
const campground = this.props.campground;
if (campground._id) {
this.props.getCampgroundDetails(campground._id);
this.setState({
name: campground.name,
description: campground.description,
cost: campground.cost
});
}
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
addCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionAddCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Campground added successfully');
};
updateCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionUpdateCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Updated successfully');
};
render() {
console.log(this.props);
return (
<Card className="add-campground-card">
<TextField
name="name"
className="textfield"
label="Campground name"
variant="outlined"
value={this.state.name}
onChange={e => this.handleChange(e)}
/>
<TextField
name="description"
className="textfield"
label="Campground description"
variant="outlined"
value={this.state.description}
onChange={e => this.handleChange(e)}
/>
<TextField
name="cost"
className="textfield"
type="number"
label="Campground cost"
variant="outlined"
value={this.state.cost}
onChange={e => this.handleChange(e)}
/>
{!this.props.campground._id ? (
<Button
variant="contained"
color="primary"
onClick={this.addCampground}>
Add Campground
</Button>
) : (
<Button
variant="contained"
color="primary"
className="update-campground-btn"
onClick={this.updateCampground}>
Update Campground
</Button>
)}
</Card>
);
}
}
const mapStateToProps = state => {
return {
campground: state.campgroundList.singleCampground || ''
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
},
dispatch
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddCampground);
campgroundList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAllCampgrounds } from './actions/campgroundActions';
import Header from '../common/Header';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import '../../styles/landingPage.css';
class CampgroundLanding extends Component {
componentDidMount() {
this.props.getAllCampgrounds();
console.log('From component did mount');
}
render() {
const { campgrounds } = this.props;
return (
<>
<Header />
{campgrounds.map(campground => (
<Card className="campground-card" key={campground._id}>
<CardActionArea>
<CardContent>
<Typography gutterBottom variant="h5"
component="h2">
{campground.name}
</Typography>
<Typography variant="body2" color="textSecondary"
component="p">
{campground.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link
style={{ textDecoration: 'none', color: 'white' }}
to={`/campgrounds/${campground._id}`}>
<Button size="small" color="primary">
View Details
</Button>
</Link>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
))}
<Link
style={{ textDecoration: 'none', color: 'white' }}
to="/campgrounds/add">
<Button color="primary">Add Campground</Button>
</Link>
</>
);
}
}
const mapStateToProps = state => {
return {
campgrounds: state.campgroundList.campgrounds
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
getAllCampgrounds
},
dispatch
);
};
export default connect(
mapStateToProps,
null
)(CampgroundLanding);
campgroundActions.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
import axios from 'axios';
const API_URL = `http://localhost:5000/api`;
export const getAllCampgrounds = () => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds`)
.then(res => {
dispatch({
type: GET_ALL_CAMPGROUNDS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionAddCampground = campground => {
return dispatch => {
axios
.post(`${API_URL}/campgrounds`, campground)
.then(res => {
console.log(res);
dispatch({
type: ADD_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const getCampgroundDetails = id => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds/${id}`)
.then(res => {
dispatch({
type: GET_CAMPGROUND_DETAILS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionUpdateCampground = id => {
return dispatch => {
axios
.put(`${API_URL}/campgrounds/${id}`)
.then(res => {
console.log(res);
dispatch({
type: EDIT_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
campgroundReducers.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
const initialState = {
campgrounds: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_ALL_CAMPGROUNDS:
const { campgroundList } = action.payload.data;
state.campgrounds = campgroundList;
return {
...state
};
case ADD_CAMPGROUND:
const { campground } = action.payload.data;
return {
...state,
campground
};
case GET_CAMPGROUND_DETAILS:
const { singleCampground } = action.payload.data;
return { ...state, singleCampground };
case EDIT_CAMPGROUND:
const { editedCampground } = action.payload.data;
return { ...state, editedCampground };
default:
return state;
}
};
If I'm using componentDidUpdate, it's leading to infinite loop.
componentDidUpdate(prevProps) {
if (prevProps.campgrounds !== this.props.campgrounds) {
this.props.getAllCampgrounds();
}
}
I know I'm going wrong somewhere, but I can't figure out where.
You have to fix your componentDidUpdate method to avoid this infinity loop. Your are trying to compare objects via === method which will always fail according to this example:
const a = {key: 1}
const b = {key: 1}
console.log(a === b); // false
You could use for example isEqual method from lodash module to compare objects like you want https://lodash.com/docs/4.17.15#isEqual
console.log(_.isEqual({key: 1}, {key: 1})) // true
console.log({key: 1} === {key: 1}) // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Actually why do you want refresh data in this case? When you add object with success into the store this will refresh your component so you don't have to request for fresh data.

Categories

Resources