Please can someone explain to me why the second implementation of my code is reading the status_code of the msg and my first implementation can't read the status_code of the msg?
First implementation
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import InputField from '../components/layout/InputField';
import { faShop, faGlobe, faHouse, faSign, faMapLocationDot, faCity, faEarthEurope, faUpload } from '@fortawesome/free-solid-svg-icons';
import Button from '../components/layout/Button';
import FormAlert from '../components/layout/FormAlert';
import { createOrUpdateStore, clearMessages } from '../reducers/storeSlice';
const CreateStore = () => {
const [formData, setFormData ] = useState({
name: '',
shop_url: '',
house: '',
street: '',
postalcode: '',
city: '',
country: ''
});
const dispatch = useDispatch();
const { loading, store, msg, errors } = useSelector((state) => state.store);
const navigate = useNavigate();
const {
name,
shop_url,
house,
street,
postalcode,
city,
country,
} = formData;
useEffect(() => {
if (msg) {
dispatch(clearMessages());
}
}, []);
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getError = (name) => {
const findError = errors.filter(error => error.param === name);
if (findErfind errorh > 0) {
const error = errors.find(error => error.param === name);
return error;
}
}
const onSubmit = (e) => {
e.preventDefault();
const result = dispatch(createOrUpdateStore(formData));
if (result) {
if (msg.status_code === '201') {
navigate('/login');
}
}
}
return (
<form onSubmit={(e) => onSubmit(e)} className='dashboard-form'>
<span className='header-text'>Create your store</span>
{JSON.stringify(msg) !== '{}' ? (<FormAlert alert={msg} />) : ''}
<InputField type='text' label='Store name' name='name' value={name} changeHandler={onChange} error={getError('name')} icon={faShop} />
<InputField type='text' label='Website address' name='shop_url' value={shop_url} changeHandler={onChange} error={getError('shop_url')} icon={faGlobe} />
<InputField type='text' label='House' name='house' value={house} changeHandler={onChange} error={getError('house')} icon={faHouse} />
<InputField type='text' label='Street' name='street' value={street} changeHandler={onChange} error={getError('street')} icon={faSign} />
<InputField type='text' label='Postalcode' name='postalcode' value={postalcode} changeHandler={onChange} error={getError('postalcode')} icon={faMapLocationDot} />
<InputField type='text' label='City' name='city' value={city} changeHandler={onChange} error={getError('city')} icon={faCity} />
<InputField type='text' label='Country' name='country' value={country} changeHandler={onChange} error={getError('country')} icon={faEarthEurope} />
<Button text='CREATE' loading={loading} icon={faUpload} />
</form>
)
}
export default CreateStore;
Second implementation
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import InputField from '../components/layout/InputField';
import { faShop, faGlobe, faHouse, faSign, faMapLocationDot, faCity, faEarthEurope, faUpload } from '@fortawesome/free-solid-svg-icons';
import Button from '../components/layout/Button';
import FormAlert from '../components/layout/FormAlert';
import { createOrUpdateStore, clearMessages } from '../reducers/storeSlice';
const CreateStore = () => {
const [formData, setFormData ] = useState({
name: '',
shop_url: '',
house: '',
street: '',
postalcode: '',
city: '',
country: ''
});
const dispatch = useDispatch();
const { loading, store, msg, errors } = useSelector((state) => state.store);
const navigate = useNavigate();
const {
name,
shop_url,
house,
street,
postalcode,
city,
country,
} = formData;
useEffect(() => {
if (msg) {
dispatch(clearMessages());
}
}, []);
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getError = (name) => {
const findError = errors.filter(error => error.param === name);
if (findError.length > 0) {
const error = errors.find(error => error.param === name);
return error;
}
}
const onSubmit = (e) => {
e.preventDefault();
dispatch(createOrUpdateStore(formData));
}
const redirectOnSuccess = (msg) => {
if (msg.status_code === '201') {
setTimeout(() => {
dispatch(clearMessages());
}, 2000);
setTimeout(() => {
dispatch(clearMessages());
navigate('/store');
}, 3000);
}
}
redirectOnSuccess(msg);
return (
<form onSubmit={(e) => onSubmit(e)} className='dashboard-form'>
<span className='header-text'>Create your store</span>
{JSON.stringify(msg) !== '{}' ? (<FormAlert alert={msg} />) : ''}
<InputField type='text' label='Store name' name='name' value={name} changeHandler={onChange} error={getError('name')} icon={faShop} />
<InputField type='text' label='Website address' name='shop_url' value={shop_url} changeHandler={onChange} error={getError('shop_url')} icon={faGlobe} />
<InputField type='text' label='House' name='house' value={house} changeHandler={onChange} error={getError('house')} icon={faHouse} />
<InputField type='text' label='Street' name='street' value={street} changeHandler={onChange} error={getError('street')} icon={faSign} />
<InputField type='text' label='Postalcode' name='postalcode' value={postalcode} changeHandler={onChange} error={getError('postalcode')} icon={faMapLocationDot} />
<InputField type='text' label='City' name='city' value={city} changeHandler={onChange} error={getError('city')} icon={faCity} />
<InputField type='text' label='Country' name='country' value={country} changeHandler={onChange} error={getError('country')} icon={faEarthEurope} />
<Button text='CREATE' loading={loading} icon={faUpload} />
</form>
)
}
export default CreateStore;
I tried several methods for getting the msg in the onSubmit function but they didn't work. So I read the documentation of the react-redux toolkit and found out that you can use data gotten from the state with useSelector, and that you can use this data in any function but it still refused to work in the onSubmit() function. And this explains why the errors array was retrieved in the getError() function.
If I had to hazard a guess it is that the createOrUpdateStore
is an asynchronous action that should be awaited, and there's a stale closure over the selected msg
state in the onSubmit
callback. The second implementation skips both these issues by not waiting for any returned Promises from createOrUpdateStore
to resolve and relying on the CreateStore
component to rerender when the redux state updates.
Assuming this asynchronous createOrUpdateStore
action returns a Promise it can be awaited. To overcome the stale msg
closure the submit handler will need to manually access the store to get the current state.
// declare submit handler async
const onSubmit = async (e) => {
e.preventDefault();
// await asynchronous action to resolve with result
const result = await dispatch(createOrUpdateStore(formData));
if (result) {
// have result, get msg from current state
const state = store.getState();
const { msg } = state.store;
if (msg.status_code === '201') {
navigate('/login');
}
}
}
The redirectOnSuccess
function is called unconditionally as an unintentional side-effect and issues other unintentional side-effect. To make these intentional side-effects the logic should be moved into a useEffect
hook with appropriate dependency.
const {
loading,
store,
msg,
errors
} = useSelector((state) => state.store);
const navigate = useNavigate();
...
useEffect(() => {
if (msg.status_code === '201') {
dispatch(clearMessages());
navigate('/store');
}
}, [msg, navigate]);