I’m having an issue when trying to handle Css Animation in a ReactJs App. I’m using Classnames to trigger the Css Animations (a bit excessive but mostly just for testing)
#messagesWrapper { position: fixed; }
#messages { position: relative; }
.message { position: relative; }
@keyframes animateShow { 0% { left: -100%; } 100% { left: 0; } }
.message.animateShow { animation-name: animateShow; animation-duration: 1000ms; }
.message.show { left: 0; }
@keyframes animateHide { 0% { left: 0; } 100% { left: -100%; } }
.message.animateHide { animation-name: animateHide; animation-duration: 1000ms; }
.message.hide { left: -100%; }
If I start with just a very simple App
const App = () => {
const {messages, addMessage} = useMessages();
const handleAddMessage = () => { addMessage("info", "Message"); }
return(
<>
<button onClick={handleAddMessage}>Add Message</button>
<Messages messages={messages} />
</>
);
}
Which is just testing the useMessages Custom Hook, mostly to handle the data inside the messages state
const useMessages = () => {
const [messageNo, setMessageNo] = useState(1);
const [messages, setMessages] = useState([]);
const addMessage = useCallback((type, text) => {
// create the message object and add to the messages state
const message = { idno: new Date().getTime(), type: type, text: `${text} ${messageNo}`, className: "" };
setMessages((prev) => [...prev, message]);
// remove after waiting
setTimeout(() => {
setMessages((prev) => prev.filter((i) => i.idno !== message.idno));
}, 4000);
// update messageNo for the next message
setMessageNo((prev) => { return prev + 1; });
}, [messageNo]);
return {messages, addMessage};
}
The Messages Component (the Parent)
const Messages = ({ messages }) => {
return(
<div id="messagesWrapper">
<div id="messages">
{messages.map((message, idx) => (
<Message key={idx} message={message} />
))}
</div>
</div>
);
}
The Message Component (the Child)
const Message = ({ message }) => {
return(
<div className={`message ${message.type} ${message.className}`}>
<p>{message.text}</p>
</div>
);
}
It seems to be working in as basic of a way as I can get it, it correctly adds each Message and removes each one after the right amount of time.
When trying to include Classname changes and updating the object in the main messages state, it seems to very nearly work (probably only by accident because the timing is sometimes slow enough).
The updated useMessages is doing several Classname updates after adding and before removing but it seems to apply in unexpected ways and I think it has to do with the update state and setTimeout not being in sync with each other.
const useMessages = () => {
const [messageNo, setMessageNo] = useState(1);
const [messages, setMessages] = useState([]);
const updateMessageClassName = useCallback((message, className) => {
message.className = className;
setMessages((prev) => prev.map((i) => (i.idno === message.idno) ? (message) : (i)));
}, []);
const removeMessage = useCallback((message) => {
setMessages((prev) => prev.filter((i) => i.idno !== message.idno));
}, []);
const addMessage = useCallback((type, text) => {
// add message object to messages state
const message = { idno: new Date().getTime(), type: type, text: `${text} ${messageNo}`, className: "hide" };
setMessages((prev) => [...prev, message]);
// apply show animation (to message object in messages state)
updateMessageClassName(message, "animateShow");
setTimeout(() => { // should be listening for animationend
updateMessageClassName(message, "show");
// remove after waiting
setTimeout(() => {
// apply hide animation (to message object in messages state)
updateMessageClassName(message, "animateHide");
setTimeout(() => { // should be listening for animationend
updateMessageClassName(message, "hide");
// remove message object from state
removeMessage(message);
}, 900);
}, 4000);
}, 900);
// update messageNo for the next message
setMessageNo((prev) => { return prev + 1; });
}, [messageNo, updateMessageClassName, removeMessage]);
return {messages, addMessage};
}
What is the correct way to handle something like this?
A better working example (in jQuery) : https://jsfiddle.net/vaptnx64/