feat(examples): add flappy-bird hook demo to React templates#2325
feat(examples): add flappy-bird hook demo to React templates#2325Huxpro merged 3 commits intolynx-family:mainfrom
Conversation
Add a `useFlappy` custom hook that demonstrates reactive physics
in ReactLynx. Tap anywhere to make the Lynx logo jump with
gravity-based flappy-bird mechanics.
- `src/lib/flappy.ts` — framework-agnostic physics engine
- `src/useFlappy.ts` — React hook wrapping createFlappy
- Updated `App.tsx` with `bindtap={jump}` and `translateY` transform
Applied to: examples/react, template-react-ts, template-react-js,
template-react-vitest-rltl-ts, template-react-vitest-rltl-js
|
📝 WalkthroughWalkthroughAdds a Flappy Bird-style physics engine (createFlappy) and corresponding useFlappy hook across multiple React template projects; integrates the hook into App components to drive logo vertical translation and binds container taps to trigger jumps. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a Flappy Bird–style “tap to jump” demo to ReactLynx example + all create-rspeedy React templates by introducing a small physics engine (createFlappy) and a React hook wrapper (useFlappy) used to animate the logo via translateY(...).
Changes:
- Add framework-agnostic flappy physics engine (
src/lib/flappy.*) and a React hook wrapper (src/useFlappy.*). - Wire the hook into each template/example
Appso tapping triggers jump and the logo animates vertically. - Apply these additions across TS/JS templates including the vitest + rltl variants, plus
examples/react.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/lib/flappy.ts | Adds TS physics engine used by the hook/template demo |
| packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/useFlappy.ts | Adds TS hook wrapper around the physics engine |
| packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/App.tsx | Uses useFlappy to animate logo + bind tap to jump |
| packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.js | Adds JS physics engine used by the hook/template demo |
| packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/useFlappy.js | Adds JS hook wrapper around the physics engine |
| packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/App.jsx | Uses useFlappy to animate logo + bind tap to jump |
| packages/rspeedy/create-rspeedy/template-react-ts/src/lib/flappy.ts | Adds TS physics engine for the standard TS template |
| packages/rspeedy/create-rspeedy/template-react-ts/src/useFlappy.ts | Adds TS hook wrapper for the standard TS template |
| packages/rspeedy/create-rspeedy/template-react-ts/src/App.tsx | Uses useFlappy to animate logo + bind tap to jump |
| packages/rspeedy/create-rspeedy/template-react-js/src/lib/flappy.js | Adds JS physics engine for the standard JS template |
| packages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.js | Adds JS hook wrapper for the standard JS template |
| packages/rspeedy/create-rspeedy/template-react-js/src/App.jsx | Uses useFlappy to animate logo + bind tap to jump |
| examples/react/src/lib/flappy.ts | Adds TS physics engine for the example app |
| examples/react/src/useFlappy.ts | Adds TS hook wrapper for the example app |
| examples/react/src/App.tsx | Uses useFlappy to animate logo + bind tap to jump |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/App.jsx
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/lib/flappy.ts
Outdated
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.js
Outdated
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/useFlappy.js
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/App.tsx
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
examples/react/src/useFlappy.ts (1)
24-38:optionsis effectively init-only.Line 30 creates the engine once and Lines 34-38 only dispose it on unmount, so later
gravity/jumpForce/stackFactor/frameMschanges never reach the engine. Recreate the engine when those fields change, or document the parameter as initialization-only.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/react/src/useFlappy.ts` around lines 24 - 38, useFlappy currently creates the FlappyEngine once (engineRef.current via createFlappy) and never recreates it when options change, so runtime updates to gravity/jumpForce/stackFactor/frameMs are ignored; update useFlappy to recreate the engine when relevant options change by moving engine creation into a useEffect that depends on the specific options fields (or a stable serialized options key), e.g. on options.gravity/options.jumpForce/options.stackFactor/options.frameMs: on each dependency change, call engineRef.current?.destroy() then set engineRef.current = createFlappy(..., options) and ensure you still clean up on unmount; alternatively, if you intend options to be initialization-only, update the doc/comment for useFlappy to state that options are immutable after mount.packages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.js (1)
18-28:optionsare init-only with the current lifecycle.
createFlappy(...)only runs whileengineRef.currentis null, and the cleanup on Lines 24-28 only fires on unmount. Latergravity/jumpForce/stackFactor/frameMschanges will be ignored unless the engine is recreated.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.js` around lines 18 - 28, The engine is only created once so later changes to init-only options (gravity, jumpForce, stackFactor, frameMs) are ignored; update the hook to recreate the engine whenever those option values change by adding a useEffect that depends on the options (or their individual keys), which on change calls engineRef.current?.destroy(), reassigns engineRef.current = createFlappy(..., options) and wires setY, and retains the existing unmount cleanup; reference engineRef.current, createFlappy, destroy, and the existing cleanup useEffect when implementing this.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/react/src/lib/flappy.ts`:
- Around line 60-63: In the jump() function change the clamping expression so
velocity is capped at the intended jumpForce: replace the Math.min usage with
Math.max when computing velocity = Math.min(velocity + jumpForce * stackFactor,
jumpForce) so the result becomes velocity = Math.max(velocity + jumpForce *
stackFactor, jumpForce); this ensures repeated taps do not push velocity past
the one-jump cap (refer to jump(), velocity, jumpForce, stackFactor, timer).
In `@packages/rspeedy/create-rspeedy/template-react-js/src/lib/flappy.js`:
- Around line 42-45: In function jump(), the current clamp uses Math.min which
is wrong for negative jumpForce; change the clamp expression that sets velocity
(the line using Math.min(velocity + jumpForce * stackFactor, jumpForce)) to use
Math.max instead so rapid taps cannot exceed the intended one-full jumpForce
cap; update the expression that computes velocity (referencing velocity,
jumpForce, stackFactor) to Math.max(...) while leaving the surrounding logic
(including the timer check) intact.
In `@packages/rspeedy/create-rspeedy/template-react-ts/src/lib/flappy.ts`:
- Around line 60-63: In the jump() function fix the clamp so velocity cannot
exceed the intended full-jump limit: replace the Math.min clamp with Math.max to
ensure velocity is clamped at least to jumpForce (so when jumpForce is negative
rapid taps don't make velocity more negative than jumpForce); update the
expression that sets velocity (currently using Math.min with velocity +
jumpForce * stackFactor) to use Math.max(velocity + jumpForce * stackFactor,
jumpForce) and leave the surrounding logic (timer check, stackFactor, jumpForce)
unchanged.
In `@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/App.jsx`:
- Around line 24-32: The test snapshot in __tests__/index.test.jsx still expects
the old component tree; update the test to reflect the new wrapper bindtap and
the Logo element's style and bindtap changes by refreshing the generated
snapshot or updating the expected JSX/HTML in the assertion for the App
component render; look for references to the App render/test block and the
previous snapshot between lines ~28-89 and re-run the snapshot update (or
replace the expected tree) so it includes the new outer view with bindtap={jump}
and the Logo element showing style={{ transform: `translateY(${logoY}px)` }} and
bindtap={onTap}.
In
`@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.js`:
- Around line 42-45: The rapid-tap clamp in function jump incorrectly uses
Math.min which, given a negative jumpForce, picks a more-negative value and
allows stacking beyond the intended single jump; change the clamp to use
Math.max so velocity = Math.max(velocity + jumpForce * stackFactor, jumpForce)
(referencing variables velocity, jumpForce, stackFactor and function jump) so
repeated taps cannot exceed one full jump; update the inline comment to reflect
clamping with Math.max when jumpForce is negative.
In `@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/App.tsx`:
- Around line 26-34: The component tree now adds a root bindtap (jump) and Logo
props (style: translateY using logoY and bindtap: onTap), so update the failing
snapshot in index.test.tsx to match the new markup; re-run the test snapshot
update (e.g., run vitest -u or your test runner's update-snapshot command) so
the snapshot reflects the new root bindtap and the Logo style/bindtap
attributes.
In
`@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/lib/flappy.ts`:
- Around line 60-63: In function jump(), the clamp uses Math.min which, given
jumpForce is negative, allows velocity to exceed the intended negative cap;
change the clamp to use Math.max so velocity = Math.max(velocity + jumpForce *
stackFactor, jumpForce) (referencing variables velocity, jumpForce, stackFactor
and the jump() function) to ensure rapid taps do not drive velocity beyond the
single full jumpForce bound; keep the existing timer logic intact.
---
Nitpick comments:
In `@examples/react/src/useFlappy.ts`:
- Around line 24-38: useFlappy currently creates the FlappyEngine once
(engineRef.current via createFlappy) and never recreates it when options change,
so runtime updates to gravity/jumpForce/stackFactor/frameMs are ignored; update
useFlappy to recreate the engine when relevant options change by moving engine
creation into a useEffect that depends on the specific options fields (or a
stable serialized options key), e.g. on
options.gravity/options.jumpForce/options.stackFactor/options.frameMs: on each
dependency change, call engineRef.current?.destroy() then set engineRef.current
= createFlappy(..., options) and ensure you still clean up on unmount;
alternatively, if you intend options to be initialization-only, update the
doc/comment for useFlappy to state that options are immutable after mount.
In `@packages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.js`:
- Around line 18-28: The engine is only created once so later changes to
init-only options (gravity, jumpForce, stackFactor, frameMs) are ignored; update
the hook to recreate the engine whenever those option values change by adding a
useEffect that depends on the options (or their individual keys), which on
change calls engineRef.current?.destroy(), reassigns engineRef.current =
createFlappy(..., options) and wires setY, and retains the existing unmount
cleanup; reference engineRef.current, createFlappy, destroy, and the existing
cleanup useEffect when implementing this.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 978866b8-0a30-4ccd-9ddf-3f58c982eea6
📒 Files selected for processing (15)
examples/react/src/App.tsxexamples/react/src/lib/flappy.tsexamples/react/src/useFlappy.tspackages/rspeedy/create-rspeedy/template-react-js/src/App.jsxpackages/rspeedy/create-rspeedy/template-react-js/src/lib/flappy.jspackages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.jspackages/rspeedy/create-rspeedy/template-react-ts/src/App.tsxpackages/rspeedy/create-rspeedy/template-react-ts/src/lib/flappy.tspackages/rspeedy/create-rspeedy/template-react-ts/src/useFlappy.tspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/App.jsxpackages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.jspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/useFlappy.jspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/App.tsxpackages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/lib/flappy.tspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/useFlappy.ts
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/App.jsx
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.js
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/App.tsx
Show resolved
Hide resolved
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/lib/flappy.ts
Show resolved
Hide resolved
Merging this PR will improve performance by 13.77%
Performance Changes
Comparing Footnotes
|
Web Explorer#8105 Bundle Size — 384.5KiB (0%).2a25145(current) vs 27f1cff main#8099(baseline) Bundle metrics
|
| Current #8105 |
Baseline #8099 |
|
|---|---|---|
155.59KiB |
155.59KiB |
|
35.1KiB |
35.1KiB |
|
0% |
0% |
|
8 |
8 |
|
8 |
8 |
|
238 |
238 |
|
16 |
16 |
|
2.98% |
2.98% |
|
4 |
4 |
|
0 |
0 |
Bundle size by type no changes
| Current #8105 |
Baseline #8099 |
|
|---|---|---|
253.55KiB |
253.55KiB |
|
95.85KiB |
95.85KiB |
|
35.1KiB |
35.1KiB |
Bundle analysis report Branch Huxpro:feat/react-flappy-hook Project dashboard
Generated by RelativeCI Documentation Report issue
- Fix Math.min → Math.max in jump() clamp across all 5 flappy engine files. With negative jumpForce, Math.min allowed velocity to exceed the intended cap on rapid taps. - Update inline snapshots in vitest-rltl templates to include the new translateY style on the Logo element. - Document that useFlappy options are init-only (read once on mount).
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/__tests__/index.test.tsx (1)
28-90:⚠️ Potential issue | 🟡 MinorAdd an interaction assertion, not just the initial snapshot.
This snapshot only locks in the initial
translateY(0px)render. If the newbindtap={jump}wiring or thelogoYupdate path breaks, the test still passes. Please extend this case to trigger a tap and assert that the Logo transform changes afterward.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/__tests__/index.test.tsx` around lines 28 - 90, The test currently only snapshots the initial render; extend it to simulate the tap that should call the bound handler and update logoY/transform. Locate the Logo element (class "Logo" or "Logo--lynx") in the test that renders the tree, dispatch a tap/click event (use the test renderer's event helper like fireEvent/triggerEvent or the rltl equivalent) to invoke bindtap={jump}, then re-query the rendered tree and assert the Logo's style.transform no longer equals "translateY(0px)" (or equals the expected translated value), verifying the jump/logoY update path works.
🧹 Nitpick comments (1)
packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/__tests__/index.test.jsx (1)
10-103: Exercise the tap path, not just the initial snapshot.This only proves the first render includes
translateY(0px). Ifbindtap={jump}stops firing orlogoYstops updating, the test still passes. Please add one interaction assertion that a tap changes the Logo transform.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/__tests__/index.test.jsx` around lines 10 - 103, The test only verifies initial render; simulate a tap on the Logo to exercise the jump handler and assert logoY/transform changes. Locate the Logo element (e.g., the <view class="Logo"> or inner <image class="Logo--lynx">) in the App test, dispatch a tap/click using fireEvent or userEvent, await the update, and then assert that the Logo's style transform is no longer "translateY(0px)" (or equals the expected translated value) to prove the jump handler (jump) updated logoY.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/react/src/lib/flappy.ts`:
- Around line 46-73: Make destroy irreversible by adding a destroyed flag
checked in loop and jump: in destroy(), set destroyed = true and
clearTimeout(timer) as now; in loop(), after calling onUpdate(y) do not schedule
setTimeout(loop, frameMs) if destroyed is true (return instead); in jump(),
no-op if destroyed is true so a late tap can't restart the loop. Reference
functions: loop, jump, destroy and the timer variable.
In `@packages/rspeedy/create-rspeedy/template-react-ts/src/useFlappy.ts`:
- Around line 43-45: The string directive inside the jump callback uses the
incorrect spelling 'background only' so the runtime will ignore it; update the
directive to the canonical hyphenated form 'background-only' inside the
useCallback for the jump function (the block that contains
engineRef.current?.jump()) so Lynx/background-only plugins recognize the thread
annotation.
---
Outside diff comments:
In
`@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/__tests__/index.test.tsx`:
- Around line 28-90: The test currently only snapshots the initial render;
extend it to simulate the tap that should call the bound handler and update
logoY/transform. Locate the Logo element (class "Logo" or "Logo--lynx") in the
test that renders the tree, dispatch a tap/click event (use the test renderer's
event helper like fireEvent/triggerEvent or the rltl equivalent) to invoke
bindtap={jump}, then re-query the rendered tree and assert the Logo's
style.transform no longer equals "translateY(0px)" (or equals the expected
translated value), verifying the jump/logoY update path works.
---
Nitpick comments:
In
`@packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/__tests__/index.test.jsx`:
- Around line 10-103: The test only verifies initial render; simulate a tap on
the Logo to exercise the jump handler and assert logoY/transform changes. Locate
the Logo element (e.g., the <view class="Logo"> or inner <image
class="Logo--lynx">) in the App test, dispatch a tap/click using fireEvent or
userEvent, await the update, and then assert that the Logo's style transform is
no longer "translateY(0px)" (or equals the expected translated value) to prove
the jump handler (jump) updated logoY.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 94a23aad-a34a-4cd4-bd4a-57d1379c1ef2
📒 Files selected for processing (12)
examples/react/src/lib/flappy.tsexamples/react/src/useFlappy.tspackages/rspeedy/create-rspeedy/template-react-js/src/lib/flappy.jspackages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.jspackages/rspeedy/create-rspeedy/template-react-ts/src/lib/flappy.tspackages/rspeedy/create-rspeedy/template-react-ts/src/useFlappy.tspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/__tests__/index.test.jsxpackages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.jspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/useFlappy.jspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/__tests__/index.test.tsxpackages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/lib/flappy.tspackages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/useFlappy.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/useFlappy.js
- packages/rspeedy/create-rspeedy/template-react-js/src/useFlappy.js
- packages/rspeedy/create-rspeedy/template-react-vitest-rltl-js/src/lib/flappy.js
- packages/rspeedy/create-rspeedy/template-react-js/src/lib/flappy.js
- packages/rspeedy/create-rspeedy/template-react-vitest-rltl-ts/src/useFlappy.ts
Summary
useFlappycustom Hook that demonstrates reactive physics in ReactLynx — tap anywhere to make the Lynx logo jump with gravity-based flappy-bird mechanicssrc/lib/flappy.tscontains a framework-agnostic physics engine;src/useFlappy.tswraps it as a React Hook withuseState/useRef/useCallbackexamples/reactand all fourcreate-rspeedyReact templates (TS, JS, vitest-rltl-ts, vitest-rltl-js)Test plan
cd examples/react && pnpm dev— tap anywhere to see logo jump and fall with gravitynpx create-rspeedy@latestwith each React template — verifyuseFlappyhook and flappy interaction workSummary by CodeRabbit