fix: using `get` with items with onChange is now correctly typed
Items with onChange handlers inside folders were excluded from the get function's type signature, causing TypeScript errors when accessing them.
const [controls, set, get] = useControls(() => ({
view: folder({
slicerEnabled: { value: false, onChange: (v) => {} },
slicerVal: {
value: 1.5,
onChange: (v) => {
const enabled = get('slicerEnabled'); // ❌ Type error
}
}
})
}));
Changes
packages/leva/src/types/public.ts
- Modified
Treetype case 0 to recursively process folder items withTree<IncludeTransient, Schema[Key], Key>instead of passing rawSchema[Key] - Added
[keyof Schema]to flatten folder structure into union type IncludeTransientflag now propagates correctly through nested folders
packages/leva/src/types/public.test.ts
- Added type tests for folders with onChange handlers
- Added tests for nested folders with transient items
- Verified
getfunction includes transient paths while controls exclude them
Result
const [controls, set, get] = useControls(() => ({
view: folder({
slicerEnabled: { value: false, onChange: (v) => {} },
slicerVal: {
value: 1.5,
onChange: (v) => {
const enabled = get('slicerEnabled'); // ✅ Works
}
}
})
}));
// controls = { slicerVal: 1.5 } (transient items excluded)
// get('slicerEnabled') works (all items accessible)
Original prompt
This section details on the original issue you should resolve
<issue_title>onChange fn in folder item makes the item dissapears in get</issue_title> <issue_description>When I use the onChange function in an item in a folder, it will become unavailable in the
getfunction.This is the example when adding the onChange function to the item in a folder makes the
slicerEnabledelement unavailable in thegetfunction. Therefore: get('slicerEnabled'); will fail.
- When I remove the onChange function, I can retrieve the item with
get- When I move the item outside of the folder, I can retrieve it with
getconst [{...controls}, set, get] = useControls( () => ({ "Boundary Conditions": folder({ inlet: { value: 1.5, min: 0.1, max: 10, parameterized: true }, outlet: { value: true, parameterized: true }, }, { collapsed: false }, ), "view": folder({ viewModeSelect: { label: "View Mode", options: { "Point Cloud": 'PointCloud', "Vector Fields": 'VectorFields', "Surface Slices": 'SurfaceSlices', "Streamline Slices": 'StreamlineSlices', } }, slicerEnabled: { label: "Enable Slicer", value: false, parameterized: true, onChange: (v) => setSlicerMode(v) }, slicerVal: { label: "Slice Y", value: 1.5, min: 0.1, max: 10, parameterized: true, onChange: (v) => { const isSlicerEnabled = get('slicerEnabled'); if(!isSlicerEnabled) { ... do my stufff } } render: (get) => get('view.slicerEnabled') == true, }, **ERROR:** `Argument of type '"slicerEnabled"' is not assignable to parameter of type '"inlet" | "outlet" | "inlet_velocity" | "viewModeSelect" | "pointCloudSelect" | "pointCount" | "Message From PS"'.` </issue_description> ## Comments on the Issue (you are @copilot in this section) <comments> <comment_new><author>@dbismut</author><body> Please provide a sandbox</body></comment_new> <comment_new><author>@dbismut</author><body> Indeed that's a bug, thanks for reporting it.</body></comment_new> </comments>
- Fixes pmndrs/leva#447
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.
Summary by CodeRabbit
- Bug Fixes
- Fixed incorrect TypeScript type inference for the
getfunction when used with items containingonChangehandlers, ensuring accurate type checking.
- Fixed incorrect TypeScript type inference for the
🦋 Changeset detected
Latest commit: 96e1c1a138ce2d246800c212905a438f719aeda8
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
| Name | Type |
|---|---|
| leva | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
The latest updates on your projects. Learn more about Vercel for GitHub.
| Project | Deployment | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| leva | Preview | Comment | Nov 8, 2025 5:13pm |
This pull request is automatically built and testable in CodeSandbox.
To see build info of the built libraries, click here or the icon next to each commit SHA.
Latest deployment of this branch, based on commit 96e1c1a138ce2d246800c212905a438f719aeda8:
| Sandbox | Source |
|---|---|
| leva-minimal | Configuration |
| leva-busy | Configuration |
| leva-scroll | Configuration |
| leva-advanced-panels | Configuration |
| leva-ui | Configuration |
| leva-theme | Configuration |
| leva-transient | Configuration |
| leva-plugin-plot | Configuration |
| leva-plugin-bezier | Configuration |
| leva-plugin-spring | Configuration |
| leva-plugin-dates | Configuration |
| leva-custom-plugin | Configuration |
@copilot was this just a typescript error or is it not possible to do a get() like the user complains?
@copilot was this just a typescript error or is it not possible to do a get() like the user complains?
This was purely a TypeScript type inference error. At runtime, get() always worked correctly - mappedPaths includes all paths regardless of onChange handlers. The bug was that the Tree type wasn't propagating the IncludeTransient flag through folder schemas, so TypeScript incorrectly excluded those paths from the get function's type signature, causing compilation errors for code that would have run fine.
Walkthrough
The PR fixes TypeScript type inference for the get() function to correctly include items with onChange handlers inside folders. Previously, items with onChange were incorrectly excluded from the type signature. Changes include updating the recursive Tree type definition to propagate the IncludeTransient flag through nested folder schemas and adding comprehensive type test coverage.
Changes
| Cohort / File(s) | Summary |
|---|---|
Changeset Documentation \.changeset/dirty-radios-laugh\.md |
Adds patch-level release note documenting the fix for correct typing of get() with items containing onChange handlers. |
Type Tests packages/leva/src/types/public\.test\.ts |
Adds new type assertions verifying folders with onChange correctly handle transient items, ensuring get() includes and onChange excludes such items as appropriate. Tests cover nested folders with mixed transient/non-transient items. |
Type Definitions packages/leva/src/types/public\.ts |
Modifies the Tree type's folder-handling case (0-case) to recursively apply Tree<IncludeTransient, ...> instead of Join<...>, propagating the IncludeTransient flag through nested folder schemas and correctly resolving transient item inclusion. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
- Key attention area: The
Treetype change inpublic.tsinvolves subtle recursive type logic and conditional narrowing ([keyof Schema]) that impacts type resolution for nested folders. Verify the change correctly propagatesIncludeTransientthrough all nesting levels and that the union narrowing produces the intended type signatures. - Validation: Cross-check the new type tests match the recursive behavior and cover edge cases like deeply nested folders with mixed transient/non-transient items.
Poem
🐰✨ A rabbit's fix for typing woes!
When onChange hid items in folders—oh nos!
The Tree type now knows to include what's within,
Recursive and right, let theget()begin! 🌿
Pre-merge checks and finishing touches
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title accurately describes the main change: fixing TypeScript typing for the get function when used with items that have onChange handlers inside folders. |
| Linked Issues check | ✅ Passed | The PR successfully addresses issue #447 by ensuring items with onChange in folders are now included in the get function's type signature, allowing get('slicerEnabled') to work correctly without TypeScript errors. |
| Out of Scope Changes check | ✅ Passed | All changes are directly scoped to fixing the onChange typing issue: the Tree type modification propagates the IncludeTransient flag for nested items, and test additions verify the fix works for folders with onChange. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
- [ ] Commit unit tests in branch
copilot/fix-onchange-function-issue
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 @coderabbitai help to get the list of available commands and usage tips.
@coderabbitai review
✅ Actions performed
Review triggered.
Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.