Search code examples
reactjsredux-saga

Toast error notification still on sign up form after returning from another page


When I have signed up failed, then I moved to the another page, the error toast appeared one more time despite the form has been cleared. For example, if i have signed up failed, then i return back to the login form on another page, then came back again to my signup form, it pops up the error toast immediately despite i have set the signup form clearly. I have try to fix but it doesn't work properly.

I have this Signup.js:

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { signupForm, resetSignupState} from '../redux/actionSignup';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';

const Signup = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const { isSignedUp, errors: signupErrors} = useSelector((state) => state.signup);

  const [formData, setFormData] = useState({ //ok
    username: '',
    email: '',
    password: '',
    confirmpassword: '',
  });

  useEffect(() => {
    dispatch(resetSignupState());
  }, [dispatch]);

  const handleChange = (e) => { //ok
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };

  const validateForm = () => { //ok
    const newErrors = {};
    if (!formData.username.trim()) {
      newErrors.username = 'Username is not valid';
    }
    if (!formData.email.includes('@gmail.com')) {
      newErrors.email = 'Invalid email!';
    }
    if (formData.password.length < 8) {
      newErrors.password = 'Password must be at least 8 characters!';
    }
    if (formData.password !== formData.confirmpassword) {
      newErrors.confirmpassword = 'Password does not match!';
    }
    return newErrors;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const newErrors = validateForm();
    
    dispatch(signupForm({ 
      ...formData, 
      errors: newErrors,
      isValid: Object.keys(newErrors).length === 0 
    }));
  };
  

  useEffect(() => {
    if (isSignedUp) {
      toast.success('Signed Up Successfully!', {
        className: 'custom-toast-success',
      });
      setFormData({
        username: '',
        email: '',
        password: '',
        confirmpassword: '',
      });
      dispatch(resetSignupState());
      navigate('/login');
    }
  }, [isSignedUp, dispatch, navigate]);

  useEffect(() => {
    if (signupErrors && Object.keys(signupErrors).length > 0) {
      toast.error('Signed Up Failed!', {
        className: 'custom-toast-error',
      });
    }
  }, [signupErrors]);

  useEffect(() => {
    dispatch(resetSignupState());
    setFormData({
      username: '',
      email: '',
      password: '',
      confirmpassword: '',
    });
  }, [dispatch]);

  return (
    <div className='signup-template d-flex justify-content-center align-items-center vh-100'>
      <div className='signupform-container p-5 rounded'>
        <form onSubmit={handleSubmit}>
          <h1 className='text-center signup-title'>Sign Up</h1>

          <div className='text-start mb-2'>
            <label htmlFor='username' className='signup-props'>Username</label>
            <input
              type='text'
              placeholder='Enter Username'
              className='form-control signup-list color-background'
              style={{ border: '1.4px solid black' }}
              id='username'
              name='username'
              value={formData.username}
              onChange={handleChange}
            />
            {signupErrors.username && <div className='error' style={{ color: 'crimson' }}>{signupErrors.username}</div>}
          </div>

          <div className='text-start mb-2'>
            <label htmlFor='email' className='signup-props'>Email</label>
            <input
              type='email'
              placeholder='Enter Email'
              className='form-control signup-list color-background'
              style={{ border: '1.4px solid black' }}
              id='email'
              name='email'
              value={formData.email}
              onChange={handleChange}
            />
            {signupErrors.email && <div className='error' style={{ color: 'crimson' }}>{signupErrors.email}</div>}
          </div>

          <div className='text-start mb-2'>
            <label htmlFor='password' className='signup-props'>Password</label>
            <input
              type='password'
              placeholder='Enter Password'
              className='form-control signup-list color-background'
              style={{ border: '1.4px solid black' }}
              id='password'
              name='password'
              value={formData.password}
              onChange={handleChange}
            />
            {signupErrors.password && <div className='error' style={{ color: 'crimson' }}>{signupErrors.password}</div>}
          </div>

          <div className='text-start mb-2'>
            <label htmlFor='confirmpassword' className='signup-props'>Confirm Password</label>
            <input
              type='password'
              placeholder='Confirm Password'
              className='form-control signup-list color-background'
              style={{ border: '1.4px solid black' }}
              id='confirmpassword'
              name='confirmpassword'
              value={formData.confirmpassword}
              onChange={handleChange}
            />
            {signupErrors.confirmpassword && <div className='error' style={{ color: 'crimson' }}>{signupErrors.confirmpassword}</div>}
          </div>

          <div className='text-center mt-3 signup-buttonsz'>
            <button type='submit' className='signup-button' name='submit'>
              Submit
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default Signup;

Please help me to regconize the problem and the way to fix it corectly


Solution

  • All of the useEffect hooks are called during the render cycle, so any side-effects from any previous effect won't be made available to the component until after they've all run and the component rerendered. I suspect here that the resetSignupState action that is dispatched during the initial render cycle to clear out any submission errors doesn't block the effect that triggers the toast from happening.

    My suggestion here would be to clear the signup state when the component unmounts. Use a useEffect hook cleanup function to do then when the component unmounts.

    Update this hook call:

    useEffect(() => {
      dispatch(resetSignupState());
    }, [dispatch]);
    

    to do the dispatch in a cleanup function:

    useEffect(() => {
      return () => {
        dispatch(resetSignupState());
      };
    }, []);
    

    You can use an empty dependency so effect runs exactly once, but since dispatch is a stable reference it should technically be safe to include it if you like (or your linter complains).

    Additionally, the following useEffect hook can, and probably should, be removed since it's not doing much of anything:

    useEffect(() => {
      dispatch(resetSignupState());
      setFormData({
        username: '',
        email: '',
        password: '',
        confirmpassword: '',
      });
    }, [dispatch]);
    

    It is not needed since the signup state should have already been cleared upon component unmount, and the formState should already have its initial state value.