skip to Main Content

I am trying to create a custom toast to show error notification.
Toast visibility is depends on the isShowed from the props.

export type ToastProps = {
  color: 'error' | 'success' | 'warning';
  message: string;
  isShowed: boolean;
};

const Toast = (props: ToastProps) => {
  const { color, message, isShowed } = props;

  const [showed, setShowed] = useState(false);

  console.log(isShowed, showed);

  useEffect(() => {
    if (isShowed) {
      setShowed(true);
    } else {
      setShowed(false);
    }
  }, [isShowed]);

  return (
    <div
      className="toast-notification-wrapper"
      style={{
        position: 'fixed',
        top: '20px',
        left: '50%',
        transform: 'translateX(-50%)',
        zIndex: 9999,
        padding: '15px',
        borderRadius: '5px',
        transition: 'all 0.3s ease-out',
        opacity: showed ? 1 : 0,
        pointerEvents: showed ? 'auto' : 'none',
        display: 'flex',
        justifyContent: 'space-between',
        minWidth: '150px',
        backgroundColor:
          color === 'error'
            ? '#dc3545'
            : color === 'success'
            ? '#28a745'
            : '#ffc107',
      }}
    >
      <p style={{ margin: 0, color: 'white', fontSize: '15px', lineHeight: 1 }}>
        {message}
      </p>
      <span
        style={{
          cursor: 'pointer',
          display: 'flex',
          alignContent: 'center',
          justifyContent: 'center',
          marginLeft: '30px',
        }}
        onClick={() => {
          setShowed(!isShowed);
        }}
      >
        <FontAwesomeIcon
          icon={faClose}
          style={{ color: 'white', fontSize: '15px' }}
        />
      </span>
    </div>
  );
};

This is my toast components

const Login = (props: { isCompleted: boolean; isLoggedIn: boolean }) => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [showPassword, setShowPassword] = useState(false);
  const navigate = useNavigate();
  const [toast, setToast] = useState({
    isShowed: false,
    color: 'error',
    message: 'failed',
  } as ToastProps);

  const { isCompleted, isLoggedIn } = props;

  if (isCompleted === false) {
    return null;
  }

  if (isLoggedIn) {
    navigate('/');
  }

  const togglePasswordVisibility = () => {
    setShowPassword(showPassword ? false : true);
  };

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    try {
      await login({ username, password });

      window.location.href = '/';
    } catch (error: any) {
      setToast({
        isShowed: true,
        color: 'error',
        message:
          error.response.data?.message ||
          'Unexpected Error. Please try again later',
      });
    }
  };

  return (
    <div className="login-form">
      <Toast
        isShowed={toast.isShowed}
        color={toast.color}
        message={toast.message}
      ></Toast>
      <div className="login-form-container">
        <h1>Login</h1>
        <form onSubmit={handleSubmit}>
          <div className="form-row">
            <label>Username</label>
            <input
              type="text"
              placeholder="Enter your username"
              required={true}
              onChange={e => setUsername(e.target.value)}
            ></input>
          </div>

          <div className="form-row">
            <label>Password</label>
            <input
              type={showPassword ? 'text' : 'password'}
              placeholder="********"
              required={true}
              onChange={e => setPassword(e.target.value)}
            ></input>
            <span
              onClick={togglePasswordVisibility}
              style={{
                position: 'absolute',
                right: '20px',
                top: 'calc(50% + 12px)',
                fontSize: '13px',
                transform: 'translateY(-50%)',
                cursor: 'pointer',
              }}
            >
              <FontAwesomeIcon
                icon={showPassword ? faEyeSlash : faEye}
                style={{ color: 'black' }}
              />
            </span>
          </div>

          <div className="button-wrapper">
            <button value={'Login'}>Login</button>
          </div>
        </form>
      </div>
    </div>
  );
};
const Login = (props: { isCompleted: boolean; isLoggedIn: boolean }) => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [showPassword, setShowPassword] = useState(false);
  const navigate = useNavigate();
  const [toast, setToast] = useState({
    isShowed: false,
    color: 'error',
    message: 'failed',
  } as ToastProps);

  const { isCompleted, isLoggedIn } = props;

  if (isCompleted === false) {
    return null;
  }

  if (isLoggedIn) {
    navigate('/');
  }

  const togglePasswordVisibility = () => {
    setShowPassword(showPassword ? false : true);
  };

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    try {
      await login({ username, password });

      window.location.href = '/';
    } catch (error: any) {
      setToast({
        isShowed: true,
        color: 'error',
        message:
          error.response.data?.message ||
          'Unexpected Error. Please try again later',
      });
    }
  };

  return (
    <div className="login-form">
      <Toast
        isShowed={toast.isShowed}
        color={toast.color}
        message={toast.message}
      ></Toast>
      <div className="login-form-container">
        <h1>Login</h1>
        <form onSubmit={handleSubmit}>
          <div className="form-row">
            <label>Username</label>
            <input
              type="text"
              placeholder="Enter your username"
              required={true}
              onChange={e => setUsername(e.target.value)}
            ></input>
          </div>

          <div className="form-row">
            <label>Password</label>
            <input
              type={showPassword ? 'text' : 'password'}
              placeholder="********"
              required={true}
              onChange={e => setPassword(e.target.value)}
            ></input>
            <span
              onClick={togglePasswordVisibility}
              style={{
                position: 'absolute',
                right: '20px',
                top: 'calc(50% + 12px)',
                fontSize: '13px',
                transform: 'translateY(-50%)',
                cursor: 'pointer',
              }}
            >
              <FontAwesomeIcon
                icon={showPassword ? faEyeSlash : faEye}
                style={{ color: 'black' }}
              />
            </span>
          </div>

          <div className="button-wrapper">
            <button value={'Login'}>Login</button>
          </div>
        </form>
      </div>
    </div>
  );
};

and this is my login form components

The toast notification worked the first time when error event triggerd.
But when the error occurred again, the toast remained invisible.
When I use the console.log to check the state, the isShowed from props is true, but the showed is false.
Please help me. Thanks in advance.

2

Answers


  1. remove useEffect block and showed state from Toast component, use following ToastProps

    export type ToastProps = {
      color: 'error' | 'success' | 'warning';
      message: string;
      handleCloseToast: ()=>void
    };
    

    use this handleCloseToast function to close toast

    and based on your toast state in parent, use following Toast props

    <Toast
            handleCloseToast={() => setToast(prev => ({...prev,isShowed:false})}
            color={toast.color}
            message={toast. Message}
     ></Toast>
    

    This is just code form of what @IdrisSelimi was explaining

    Login or Signup to reply.
  2. Your useEffect code is off. It triggers on component mount and each time this component re-renders. Each time isShowed updates your useEffect updates it again but to the same value which triggers a re-render which triggers useEffect again, which is a bad infinite loop. You could just get rid of the useEffect and call your setter function from within some event like onClick or from a callback in a function, depending on how you wanna implement it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search