leva icon indicating copy to clipboard operation
leva copied to clipboard

fix: using `get` with items with onChange is now correctly typed

Open Copilot opened this issue 1 month ago • 8 comments

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 Tree type case 0 to recursively process folder items with Tree<IncludeTransient, Schema[Key], Key> instead of passing raw Schema[Key]
  • Added [keyof Schema] to flatten folder structure into union type
  • IncludeTransient flag 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 get function 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 get function.

This is the example when adding the onChange function to the item in a folder makes the slicerEnabled element unavailable in the get function. 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 get

const [{...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 get function when used with items containing onChange handlers, ensuring accurate type checking.

Copilot avatar Nov 02 '25 15:11 Copilot

🦋 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

changeset-bot[bot] avatar Nov 02 '25 15:11 changeset-bot[bot]

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
leva Ready Ready Preview Comment Nov 8, 2025 5:13pm

vercel[bot] avatar Nov 02 '25 15:11 vercel[bot]

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

codesandbox-ci[bot] avatar Nov 02 '25 15:11 codesandbox-ci[bot]

@copilot was this just a typescript error or is it not possible to do a get() like the user complains?

gsimone avatar Nov 08 '25 12:11 gsimone

@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.

Copilot avatar Nov 08 '25 12:11 Copilot

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 Tree type change in public.ts involves subtle recursive type logic and conditional narrowing ([keyof Schema]) that impacts type resolution for nested folders. Verify the change correctly propagates IncludeTransient through 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 the get() 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Nov 08 '25 17:11 coderabbitai[bot]

@coderabbitai review

gsimone avatar Nov 08 '25 17:11 gsimone

✅ 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.

coderabbitai[bot] avatar Nov 08 '25 17:11 coderabbitai[bot]