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
remove
useEffect
block andshowed
state from Toast component, use following ToastPropsuse this handleCloseToast function to close toast
and based on your toast state in parent, use following Toast props
This is just code form of what @IdrisSelimi was explaining
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.