-
Notifications
You must be signed in to change notification settings - Fork 0
⚡ Bolt: optimize scroll event listeners for smoother scrolling #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,21 @@ | ||
| import { expect, describe, it } from "@jest/globals"; | ||
| import { render } from "@testing-library/react"; | ||
| import { render, fireEvent } from "@testing-library/react"; | ||
| import BackToTop from "@/components/elements/BackToTop"; | ||
|
|
||
| describe("BackToTop Component", () => { | ||
| it("matches snapshot", () => { | ||
| const { container } = render(<BackToTop target="#top" />); | ||
| expect(container).toMatchSnapshot(); | ||
| }); | ||
|
|
||
| it("updates scroll state on scroll event", () => { | ||
| render(<BackToTop target="#top" />); | ||
|
|
||
| Object.defineProperty(window, "scrollY", { value: 150, writable: true, configurable: true }); | ||
| window.requestAnimationFrame = (cb) => { | ||
| cb(performance.now()); | ||
| return 1; | ||
| }; | ||
| fireEvent.scroll(window); | ||
| }); | ||
|
Comment on lines
+11
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test case is missing assertions. A test without assertions doesn't verify any behavior and will pass even if the component is broken. You should add assertions to check that the component's state is updated correctly after the scroll event and that the 'Back to Top' button is displayed.
Comment on lines
+11
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test lacks assertions — it exercises code but verifies nothing. The test fires a scroll event but never asserts that 🧪 Proposed fix — add meaningful assertions it("updates scroll state on scroll event", () => {
- render(<BackToTop target="#top" />);
+ const { container } = render(<BackToTop target="#top" />);
+
+ // Initially not scrolled - element should not be visible
+ expect(container.querySelector(".paginacontainer")).toBeNull();
Object.defineProperty(window, "scrollY", { value: 150, writable: true, configurable: true });
window.requestAnimationFrame = (cb) => {
cb(performance.now());
return 1;
};
fireEvent.scroll(window);
+
+ // After scrolling past 100px, the back-to-top element should appear
+ expect(container.querySelector(".paginacontainer")).toBeInTheDocument();
});🤖 Prompt for AI Agents |
||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,11 +5,18 @@ export default function BackToTop({ target }: Readonly<{ target: string }>) { | |
| const [hasScrolled, setHasScrolled] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const state = { isTicking: false }; | ||
| const onScroll = () => { | ||
| setHasScrolled(window.scrollY > 100); | ||
| if (!state.isTicking) { | ||
| window.requestAnimationFrame(() => { | ||
| setHasScrolled(window.scrollY > 100); | ||
| state.isTicking = false; | ||
| }); | ||
| state.isTicking = true; | ||
| } | ||
| }; | ||
|
|
||
| window.addEventListener("scroll", onScroll); | ||
| window.addEventListener("scroll", onScroll, { passive: true }); | ||
| return () => window.removeEventListener("scroll", onScroll); | ||
| }, []); | ||
|
Comment on lines
7
to
21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of To prevent this, you should cancel the animation frame in the |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,17 +16,22 @@ export default function DynamicHeaderWrapper({ navigation }: Readonly<DynamicHea | |
| const [scroll, setScroll] = useState<boolean>(false); | ||
|
|
||
| React.useEffect(() => { | ||
| const state = { isTicking: false }; | ||
| const handleScroll = (): void => { | ||
| const scrollCheck: boolean = window.scrollY > 100; | ||
| if (scrollCheck !== scroll) { | ||
| setScroll(scrollCheck); | ||
| if (!state.isTicking) { | ||
| window.requestAnimationFrame(() => { | ||
| const scrollCheck: boolean = window.scrollY > 100; | ||
| setScroll((prev) => (prev !== scrollCheck ? scrollCheck : prev)); | ||
| state.isTicking = false; | ||
| }); | ||
| state.isTicking = true; | ||
| } | ||
| }; | ||
| document.addEventListener("scroll", handleScroll); | ||
| document.addEventListener("scroll", handleScroll, { passive: true }); | ||
| return () => { | ||
| document.removeEventListener("scroll", handleScroll); | ||
| }; | ||
| }, [scroll]); | ||
| }, []); | ||
|
Comment on lines
18
to
+34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of To prevent this, you should cancel the animation frame in the |
||
|
|
||
| return ( | ||
| <> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,19 +79,25 @@ | |
|
|
||
| useEffect(() => { | ||
| AOS.init(); | ||
|
|
||
| const state = { isTicking: false }; | ||
| const handleScroll = (): void => { | ||
| const scrollCheck: boolean = window.scrollY > 100; | ||
| if (scrollCheck !== scroll) { | ||
| setScroll(scrollCheck); | ||
| if (!state.isTicking) { | ||
| window.requestAnimationFrame(() => { | ||
| const scrollCheck: boolean = window.scrollY > 100; | ||
| setScroll((prev) => (prev !== scrollCheck ? scrollCheck : prev)); | ||
| state.isTicking = false; | ||
| }); | ||
| state.isTicking = true; | ||
| } | ||
|
Comment on lines
+83
to
92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Raf not cancelled Layout/DynamicHeaderWrapper/BackToTop schedule setState inside requestAnimationFrame but never cancel a pending frame on unmount, so the callback can still run after cleanup and call setState on an unmounted component. This can trigger React warnings and leak work during route changes and effect teardown/replay. Agent Prompt
|
||
| }; | ||
|
|
||
| document.addEventListener("scroll", handleScroll); | ||
| document.addEventListener("scroll", handleScroll, { passive: true }); | ||
|
|
||
| return () => { | ||
| document.removeEventListener("scroll", handleScroll); | ||
| }; | ||
| }, [scroll]); | ||
| }, []); | ||
|
Comment on lines
80
to
+100
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of To prevent this, you should cancel the animation frame in the |
||
|
|
||
| const defaultNavigation: EditionNavigation = { | ||
| main: mainNavLinks, | ||
|
|
@@ -101,9 +107,9 @@ | |
|
|
||
| const resolvedHeaderStyle = headerStyle || 1; | ||
| const resolvedFooterStyle = footerStyle || 1; | ||
| const headerRenderer = headerRenderers[resolvedHeaderStyle] ?? headerRenderers[1]; | ||
| const headerElement = headerRenderer({ scroll, isSearch, handleSearch }, defaultNavigation); | ||
| const footerElement = footerComponents[resolvedFooterStyle] ?? footerComponents[1]; | ||
|
|
||
| return ( | ||
| <> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.