I want to show an animated text next to the button upon hovering the button. This works fine when I am using click
event on button but doesn't work when using mouseenter
and mouseleave
events.
I'm animating using CSS transition
-
#text-container {
transition: max-width 3s ease;
overflow-x: hidden;
}
App.tsx
file -
import { useEffect, useState } from 'react';
import './App.css';
const TextComponent = () => {
const [maxWidth, setMaxWidth] = useState('0vw');
useEffect(() => {
setMaxWidth('100vw');
}, []);
return (
<div id="text-container" style={{ maxWidth, paddingLeft: '8px' }}>
<div style={{ textWrap: 'nowrap' }}>Sample text??</div>
</div>
);
};
function App() {
const [show, setShow] = useState(false);
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
width: '300px',
}}
>
<button
onClick={() => setShow((prevShow) => !prevShow)}
// onMouseEnter={() => setShow(true)}
// onMouseLeave={() => setShow(false)}
>
Show Text
</button>
{show ? <TextComponent /> : null}
</div>
);
}
export default App;
StackBlitz - link
https://stackblitz.com/edit/vitejs-vite-osndza?file=src%2FApp.tsx
When using these events how React handles component re-renders with the mouseenter
and mouseleave
events, React adds/removes the TextComponent, causing the CSS transition to restart each time.
I have checked in StackBlitz working fine
App.css
)#text-container {
max-width: 0;
transition: max-width 0.5s ease;
overflow-x: hidden;
white-space: nowrap;
padding-left: 8px;
}
#text-container.show {
max-width: 100vw;
transition: max-width 3s ease;
}
import { useState } from 'react';
import './App.css';
const TextComponent = ({ show }) => (
<div id="text-container" className={show ? 'show' : ''}>
<div>Sample text??</div>
</div>
);
function App() {
const [show, setShow] = useState(false);
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
width: '300px',
}}
>
<button
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
>
Show Text
</button>
<TextComponent show={show} />
</div>
);
}
export default App;
show
state now toggles a CSS class (show
) on #text-container
.max-width
transition smoothly, even when the component is always rendered, preventing re-renders from disrupting the animation.Solution 2:
To avoid always mounting TextComponent
, you can use CSS transitions with inline styles but conditionally render the component only when the transition ends.
App.css
)#text-container {
max-width: 0;
transition: max-width 3s ease;
overflow-x: hidden;
white-space: nowrap;
padding-left: 8px;
}
#text-container.show {
max-width: 100vw;
}
import { useEffect, useState } from 'react';
import './App.css';
const TextComponent = ({ show, onTransitionEnd }) => (
<div
id="text-container"
className={show ? 'show' : ''}
onTransitionEnd={onTransitionEnd}
>
<div>Sample text??</div>
</div>
);
function App() {
const [show, setShow] = useState(false);
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
if (show) setIsMounted(true);
}, [show]);
const handleTransitionEnd = () => {
if (!show) setIsMounted(false);
};
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
width: '300px',
}}
>
<button
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
>
Show Text
</button>
{isMounted && (
<TextComponent show={show} onTransitionEnd={handleTransitionEnd} />
)}
</div>
);
}
export default App;
isMounted
controls whether TextComponent
is in the DOM.show
triggers the transition, and onTransitionEnd
removes TextComponent
from the DOM only after the exit transition completes.