I want to animate an element when the user clicks on a button. The element displays a count of how many times the button has been clicked. This is managed through a state that is incremented on every button click.
Whenever the button is clicked, the displayed number should bounce/animate, to emphasize its change.
In essence I want to conditionally append a CSS class. There's many examples of how to do this, but almost all of them only have a single class applied to them, not lots of classes.
I use tailwindcss for styling. This means lots of CSS classes on lots of elements.
What I have considered
String concatenation
Having a classNames
array, where the animation class can be added and removed easily.
const [count, setCount] = useState(0);
const [classNames, setClassNames] = useState([
'text-2xl', 'mb-2', 'font-bold', 'text-black', 'dark:text-white'
]);
const countElement = <p className={classNames.join(' ')}>{count}</p>;
const handleClick = () => {
setCount(count + 1);
// Append the animation class.
setClassNames(classNames.concat('animate-bounce'));
// Remove the animation class after the animation is done.
setTimeout(() => {
setClassNames(classNames.filter(className =>
className !== 'animate-bounce'));
}, 1000);
};
return <>
{count > 0 ? countElement : null}
<button className="px-4 py-3 mb-2 rounded-lg font-semibold transition-colors duration-300
bg-cardinal-default hover:bg-cardinal-dark active:bg-cardinal-darkest
text-black dark:text-white"
onClick={handleClick}>Click me</button>
</>;
Though with tailwindcss this is kind of ugly as it separates all of the styling from its actual element. Not exactly ideal. It feels very hacky.
Animate an inner element
const [isBouncing, setIsBouncing] = useState(false);
const countElement = (
<p className="text-2xl mb-2 font-bold text-black dark:text-white">
<span className={isBouncing ? 'inline-block animate-spin' : ''}>{count}</span>
</p>
);
...
setCount(count + 1);
setIsBouncing(true);
setTimeout(() => setIsBouncing(false), 1000);
...
This is better, yet it still requires me to set inline-block
Is there a better way of doing this? Or have I exhausted all worthwhile solutions?