1

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.

2 Answers2

0

You have to use a preventDefault to prevent the default submit action and it’s done by the code below:

Const onClick=(event)=>{
    event.preventDefault()
    // then you can have your redux function calls
}
zahra shahrouzi
  • 862
  • 7
  • 18
0

Issues

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.

Solution to fix implementation 1

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');
    }
  }
}

Solution to fix implementation 2

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]);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thank you so much for your feedback. Initially, I was getting an error on my console when I added the "msg" and the "navigate" parameters to my useEffect dependencies. The error was saying "react-dom.development.js:27292 Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops." on my browser console. That was why I stayed away from the useEffect implementation, but I tried your fix for implementation 2 and it worked. – coder-black Feb 03 '23 at 11:59