Add auto-cycling marquee effect to footer solution and comparison links#3647
Add auto-cycling marquee effect to footer solution and comparison links#3647plainspace wants to merge 10 commits intofastrepl:mainfrom
Conversation
👷 Deploy request for hyprnote pending review.Visit the deploys page to approve it
|
✅ Deploy Preview for hyprnote-storybook canceled.
|
apps/web/src/components/footer.tsx
Outdated
| const useCaseInterval = setInterval(() => { | ||
| setIsUseCaseFading(true); | ||
| setTimeout(() => { | ||
| setUseCaseIndex((prev) => | ||
| getNextRandomIndex(useCasesList.length, prev), | ||
| ); | ||
| setIsUseCaseFading(false); | ||
| }, 500); | ||
| }, 2000); | ||
|
|
||
| let vsInterval: ReturnType<typeof setInterval>; | ||
| const vsDelay = setTimeout(() => { | ||
| vsInterval = setInterval(() => { | ||
| setIsVsFading(true); | ||
| setTimeout(() => { | ||
| setVsIndex((prev) => getNextRandomIndex(vsList.length, prev)); | ||
| setIsVsFading(false); | ||
| }, 500); | ||
| }, 2000); | ||
| }, 1000); | ||
|
|
||
| return () => { | ||
| clearInterval(useCaseInterval); | ||
| clearTimeout(vsDelay); | ||
| if (vsInterval) clearInterval(vsInterval); | ||
| }; |
There was a problem hiding this comment.
Critical Bug: Memory leak and state updates on unmounted component
The nested setTimeout calls inside each interval are not tracked or cleaned up. If the component unmounts while a setTimeout is pending (e.g., during the 500ms fade transition), those callbacks will still execute and attempt to call setUseCaseIndex, setIsUseCaseFading, setVsIndex, and setIsVsFading on an unmounted component.
What breaks: React will log warnings about setting state on unmounted components, and the timeouts will continue running after the component is destroyed, causing memory leaks.
Fix: Track the timeout IDs and clear them in the cleanup function:
const timeoutIds = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
useEffect(() => {
// ... existing initialization code ...
const useCaseInterval = setInterval(() => {
setIsUseCaseFading(true);
const tid = setTimeout(() => {
setUseCaseIndex((prev) => getNextRandomIndex(useCasesList.length, prev));
setIsUseCaseFading(false);
timeoutIds.current.delete(tid);
}, 500);
timeoutIds.current.add(tid);
}, 2000);
// ... similar for vsInterval ...
return () => {
clearInterval(useCaseInterval);
clearTimeout(vsDelay);
if (vsInterval) clearInterval(vsInterval);
timeoutIds.current.forEach(clearTimeout);
timeoutIds.current.clear();
};
}, []);Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
What changed
Replaced the hover-based blur effect on the footer's "for [use case]" and "vs. [competitor]" links with an auto-cycling marquee that smoothly fades between different options.
Why we did it
Improved discoverability: The previous implementation required users to hover over the links to see different options, which was not discoverable and required interaction. Many users likely never knew multiple use cases and comparisons existed.
Better engagement: The auto-cycling effect highlights the variety of solutions and comparisons offered, without requiring any user action. It subtly showcases our different market segments (Sales, Recruiting, Consulting, etc.) and competitive positioning.
More elegant UX: The blur effect felt jarring and unclear. The new smooth fade transitions (500ms duration) feel more polished and elegant, aligning with the site's overall design aesthetic.
Technical details
Extras
PS I couldn't access contributing guidlines so I'm YOLOing this.