Skip to content

feat: add noCache option to clear cached values#600

Open
RodrigoHamuy wants to merge 15 commits intopmndrs:mainfrom
RodrigoHamuy:feat/clear-path
Open

feat: add noCache option to clear cached values#600
RodrigoHamuy wants to merge 15 commits intopmndrs:mainfrom
RodrigoHamuy:feat/clear-path

Conversation

@RodrigoHamuy
Copy link
Contributor

@RodrigoHamuy RodrigoHamuy commented Feb 26, 2026

Summary

  • Adds store.clearPath(path) — new method to imperatively discard the cached value of an input that is no longer mounted
  • Adds noCache to InputOptions — per-input flag; when true the input's cached value is discarded from the store on unmount
  • Adds noCache to LevaRootProps — panel-level flag; when true all inputs managed by that panel have their cached values discarded on unmount, overriding the per-input setting
  • Added unit tests and storybook examples and updated docs.

Summary by CodeRabbit

  • New Features

    • Added noCache option to automatically clear individual input values from cache on unmount.
    • Added noCache prop to panels to clear all cached values on unmount.
    • Added store.clearPath() method for imperatively removing specific cached values.
  • Documentation

    • Added guide showing three methods for clearing cached input values with usage examples.

RodrigoHamuy and others added 5 commits February 25, 2026 15:53
- Add `store.clearPath(path)` to hard-delete a cached path, guarded by
  `__refCount` so it's a no-op while the path is still mounted
- Add `clearOnUnmount?: boolean` per-input schema option that
  automatically clears the path when the component unmounts
- Use a `Set` for clearOnUnmountPaths so add/remove correctly handles
  dynamic changes to the option between renders
- Add integration tests covering mount/unmount lifecycle, nested folder
  paths, value preservation, and both clearOnUnmount mechanisms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `clearOnUnmount` prop to `LevaRootProps` that overrides individual
  input settings, clearing all paths from the store on component unmount
- Add `clearOnUnmount` / `setClearOnUnmount` to `StoreType` and `Store`
  so the flag is readable at cleanup time without needing reactive state
- Read `store.clearOnUnmount` in the useControls cleanup, falling back
  to the per-input Set when the store-level flag is false
- Add test covering the store-level flag via `levaStore.setClearOnUnmount`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2026

🦋 Changeset detected

Latest commit: 41b0ed9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
leva Minor
@leva-ui/plugin-bezier Major
@leva-ui/plugin-dates Major
@leva-ui/plugin-plot Major
@leva-ui/plugin-spring Major

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

@vercel
Copy link

vercel bot commented Feb 26, 2026

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

Project Deployment Actions Updated (UTC)
leva Ready Ready Preview, Comment Mar 3, 2026 3:17pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a noCache feature enabling inputs to discard cached values on unmount across three mechanisms: panel-level via LevaRootProps.noCache, per-input via InputOptions.noCache, and imperative via store.clearPath(). Store logic, type definitions, component integration, input processing utilities, and plugin compatibility are updated accordingly.

Changes

Cohort / File(s) Summary
Core Store & Types
packages/leva/src/store.ts, packages/leva/src/types/internal.ts, packages/leva/src/types/public.ts
Added noCache boolean property to Store, implemented setNoCache(flag) method, added clearPath(path) method that removes cached values when reference count is zero. Updated StoreType and MappedPaths type definitions to reflect new properties and methods.
Component Integration
packages/leva/src/components/Leva/LevaRoot.tsx
Added useEffect import and wired noCache prop to store via store.setNoCache() with cleanup on unmount. Destructured noCache parameter with false default.
Input Processing Pipeline
packages/leva/src/useControls.ts, packages/leva/src/utils/data.ts, packages/leva/src/utils/input.ts
Extended useControls to track noCachePaths as sixth return element; added conditional path clearing on unmount based on store.noCache or per-path settings. Propagated noCache through getDataFromSchema and parseOptions to surface flag in MappedPaths and CommonOptions.
Plugin Updates
packages/plugin-plot/src/plot-plugin.ts
Changed Data import to type-only; updated sanitize function signature to accept structural type { get: (path: string) => any } instead of StoreType.
Tests & Stories
packages/leva/src/useControls.test.tsx, packages/leva/stories/clear-on-unmount.stories.tsx
Added 189-line integration test suite covering mount/unmount lifecycle, nested paths, and noCache scenarios across input and panel levels. Added 178-line Storybook file with four stories demonstrating per-input, panel-level, override, and imperative clearPath behaviors.
Documentation
docs/getting-started/configuration.md, docs/getting-started/inputs.md
Added panel-level and per-input noCache documentation with usage examples; created "Clearing Cached Values on Unmount" section covering three clearing mechanisms: panel-level, per-input, and imperative store.clearPath().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~40 minutes

Poem

🐰 A cache is cleared when inputs part,
No traces left, a fresh new start,
Per-input, panel, or by hand—
The stored-away values now unplanned!
With unmount hooks and cleanup's call,
We make sure nothing clings at all.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding a noCache option to clear cached input values. It reflects the primary change across multiple files and components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Feb 26, 2026

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 41b0ed9:

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

RodrigoHamuy and others added 2 commits February 26, 2026 12:04
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/leva/src/useControls.test.tsx (1)

133-148: Consider adding a regression test for LevaRoot prop lifecycle wiring.

You already test store.setClearOnUnmount(true) directly; adding one test that mounts/unmounts <LevaRoot clearOnUnmount /> would better protect the prop-to-store lifecycle behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leva/src/useControls.test.tsx` around lines 133 - 148, Add a
regression test that verifies the LevaRoot prop wiring to the store by mounting
and unmounting <LevaRoot clearOnUnmount /> instead of calling
levaStore.setClearOnUnmount(true) directly: render a component tree wrapped with
LevaRoot (with clearOnUnmount prop), change a control value via
levaStore.setValueAtPath('myNumber', 42, true), unmount the wrapper, then
remount the same control and assert it reset to its default (like the existing
NumberComponent assertions). Reference LevaRoot, levaStore, setValueAtPath and
NumberComponent to locate where to add this test so the prop-to-store lifecycle
behavior is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/leva/src/components/Leva/LevaRoot.tsx`:
- Around line 110-112: The effect sets store.setClearOnUnmount(clearOnUnmount)
but never restores the previous flag, so capture the store's prior value before
setting and restore it in the effect cleanup to avoid leaving stale state when
this root unmounts or the store instance changes; specifically, in the useEffect
that calls store.setClearOnUnmount(clearOnUnmount) save the previous value
(e.g., const prev = store.clearOnUnmount or call a getter if available) then
return a cleanup that calls store.setClearOnUnmount(prev) (or sets false if no
getter exists), and ensure this logic runs whenever store or clearOnUnmount
changes.

---

Nitpick comments:
In `@packages/leva/src/useControls.test.tsx`:
- Around line 133-148: Add a regression test that verifies the LevaRoot prop
wiring to the store by mounting and unmounting <LevaRoot clearOnUnmount />
instead of calling levaStore.setClearOnUnmount(true) directly: render a
component tree wrapped with LevaRoot (with clearOnUnmount prop), change a
control value via levaStore.setValueAtPath('myNumber', 42, true), unmount the
wrapper, then remount the same control and assert it reset to its default (like
the existing NumberComponent assertions). Reference LevaRoot, levaStore,
setValueAtPath and NumberComponent to locate where to add this test so the
prop-to-store lifecycle behavior is covered.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 402a1e9 and c9e334b.

📒 Files selected for processing (9)
  • .changeset/clear-path-on-unmount.md
  • packages/leva/src/components/Leva/LevaRoot.tsx
  • packages/leva/src/store.ts
  • packages/leva/src/types/internal.ts
  • packages/leva/src/types/public.ts
  • packages/leva/src/useControls.test.tsx
  • packages/leva/src/useControls.ts
  • packages/leva/src/utils/data.ts
  • packages/leva/src/utils/input.ts

@RodrigoHamuy RodrigoHamuy marked this pull request as draft February 27, 2026 10:52
- Add clear-on-unmount.stories.tsx with 4 stories covering per-input,
  panel-level, and imperative (store.clearPath) usage
- Document clearOnUnmount input option in inputs.md
- Document clearOnUnmount panel prop and store.clearPath in configuration.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The useEffect cleanup now resets the store flag to false when the panel
unmounts (or the store prop changes), preventing stale clearOnUnmount
state on custom stores.

Also adds a test that exercises the prop→store wiring through LevaPanel
rather than calling setClearOnUnmount directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@RodrigoHamuy RodrigoHamuy marked this pull request as ready for review February 27, 2026 14:06
@RodrigoHamuy RodrigoHamuy changed the title feat: add clearOnUnmount option to remove inputs from store on unmount feat: add clearOnUnmount option to remove inputs from store on unmount Feb 27, 2026
@RodrigoHamuy RodrigoHamuy changed the title feat: add clearOnUnmount option to remove inputs from store on unmount feat: add clearOnUnmount option to clear cached values Feb 27, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/leva/stories/clear-on-unmount.stories.tsx (1)

137-168: Make ClearPath remount behavior directly testable in-story.

The comment says “next time the component remounts,” but this story has no mount/unmount control. Add a toggle so the stated behavior is demonstrable without leaving the story.

Suggested story tweak
 export const ClearPath: StoryFn = () => {
   const store = useCreateStore()
+  const [mounted, setMounted] = React.useState(true)

-  const values = useControls({ position: { x: 0, y: 0 }, speed: 1 }, { store })
+  const values = useControls({ position: { x: 0, y: 0 }, speed: 1 }, { store })

   return (
     <div>
       <LevaPanel store={store} />
+      <button onClick={() => setMounted((m) => !m)}>{mounted ? 'Unmount' : 'Mount'}</button>
-      <div style={{ padding: 20, border: '1px solid `#444`', margin: '10px 0' }}>
+      {mounted && <div style={{ padding: 20, border: '1px solid `#444`', margin: '10px 0' }}>
         <p>
           <strong>store.clearPath</strong> imperatively discards a single cached input.
         </p>
         <pre>{JSON.stringify(values, null, '  ')}</pre>
-      </div>
+      </div>}
       <button
         onClick={() => {
           // Discard the cached position; next mount will use the initial value.
           store.clearPath('position')
         }}>
         Clear position cache
       </button>
     </div>
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leva/stories/clear-on-unmount.stories.tsx` around lines 137 - 168,
The ClearPath story describes remount behavior but offers no way to
mount/unmount; modify the ClearPath component to add a local boolean state (e.g.
isMounted via useState) and a "Toggle mount" button, then render the LevaPanel
and the controls block conditionally based on isMounted so you can unmount and
remount the UI in-story; keep using useCreateStore() to create the store and
call store.clearPath('position') from the existing "Clear position cache" button
so testers can clear the cached value and then toggle remount to observe the
initial value re-applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/leva/stories/clear-on-unmount.stories.tsx`:
- Around line 137-168: The ClearPath story describes remount behavior but offers
no way to mount/unmount; modify the ClearPath component to add a local boolean
state (e.g. isMounted via useState) and a "Toggle mount" button, then render the
LevaPanel and the controls block conditionally based on isMounted so you can
unmount and remount the UI in-story; keep using useCreateStore() to create the
store and call store.clearPath('position') from the existing "Clear position
cache" button so testers can clear the cached value and then toggle remount to
observe the initial value re-applied.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9e334b and e3e49e0.

📒 Files selected for processing (5)
  • docs/getting-started/configuration.md
  • docs/getting-started/inputs.md
  • packages/leva/src/components/Leva/LevaRoot.tsx
  • packages/leva/src/useControls.test.tsx
  • packages/leva/stories/clear-on-unmount.stories.tsx
✅ Files skipped from review due to trivial changes (1)
  • docs/getting-started/inputs.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/leva/src/useControls.test.tsx

@RodrigoHamuy RodrigoHamuy changed the title feat: add clearOnUnmount option to clear cached values feat: add noCache option to clear cached values Mar 2, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/leva/src/useControls.ts (2)

208-213: Cleanup logic is correct; consider silencing the lint warning.

The static analysis flags that the forEach callback should not return a value. While store.clearPath returns void, the setState inside it technically returns the new state which could be interpreted as a return value.

You can silence the lint by wrapping in braces or using for...of:

♻️ Optional: use for...of to avoid lint warning
     return () => {
       store.disposePaths(paths)
       const pathsToClear = store.noCache ? paths : [...noCachePaths]
-      pathsToClear.forEach((path) => store.clearPath(path))
+      for (const path of pathsToClear) {
+        store.clearPath(path)
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leva/src/useControls.ts` around lines 208 - 213, The lint warning
about returning a value from the forEach callback can be silenced by avoiding an
implicit return; update the cleanup to iterate without returning by replacing
pathsToClear.forEach((path) => store.clearPath(path)) with an explicit block or
a for...of loop so the callback body does not implicitly return anything—apply
this change around the existing store.disposePaths(paths) and ensure you keep
references to store.clearPath and pathsToClear (or noCachePaths) intact.

161-165: Redundant delete call in fresh Set.

The noCachePaths Set is created fresh on each memo computation (line 142), so the else branch calling delete is a no-op—the path can't exist in a newly created Set.

♻️ Suggested simplification
       if (noCache) {
         noCachePaths.add(path)
-      } else {
-        noCachePaths.delete(path)
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/leva/src/useControls.ts` around lines 161 - 165, The else branch
calling noCachePaths.delete(path) is redundant because noCachePaths is a freshly
created Set during the memo; remove the else/delete call and simplify the block
so you only add the path when noCache is true (identify the variables
noCachePaths and the conditional around noCache in useControls.ts to locate the
change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/leva/stories/clear-on-unmount.stories.tsx`:
- Around line 145-168: The ClearPath story currently calls
store.clearPath('position') while useControls is still mounted so the call is a
no-op; modify the story (ClearPath) to demonstrate clearing by adding an
unmount/remount toggle: render the useControls + LevaPanel behind a conditional
controlled by a boolean state (e.g., isMounted) created with useState, move the
"Clear position cache" button so it first sets isMounted to false (unmounting
the controls) and then calls store.clearPath('position') (or provide two
buttons: one to unmount/remount and one to clear while unmounted), keeping
references to useCreateStore and store.clearPath unchanged so the demo shows the
cache being cleared on unmount.

---

Nitpick comments:
In `@packages/leva/src/useControls.ts`:
- Around line 208-213: The lint warning about returning a value from the forEach
callback can be silenced by avoiding an implicit return; update the cleanup to
iterate without returning by replacing pathsToClear.forEach((path) =>
store.clearPath(path)) with an explicit block or a for...of loop so the callback
body does not implicitly return anything—apply this change around the existing
store.disposePaths(paths) and ensure you keep references to store.clearPath and
pathsToClear (or noCachePaths) intact.
- Around line 161-165: The else branch calling noCachePaths.delete(path) is
redundant because noCachePaths is a freshly created Set during the memo; remove
the else/delete call and simplify the block so you only add the path when
noCache is true (identify the variables noCachePaths and the conditional around
noCache in useControls.ts to locate the change).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3e49e0 and aa68a77.

📒 Files selected for processing (12)
  • .changeset/clear-path-on-unmount.md
  • docs/getting-started/configuration.md
  • docs/getting-started/inputs.md
  • packages/leva/src/components/Leva/LevaRoot.tsx
  • packages/leva/src/store.ts
  • packages/leva/src/types/internal.ts
  • packages/leva/src/types/public.ts
  • packages/leva/src/useControls.test.tsx
  • packages/leva/src/useControls.ts
  • packages/leva/src/utils/data.ts
  • packages/leva/src/utils/input.ts
  • packages/leva/stories/clear-on-unmount.stories.tsx
✅ Files skipped from review due to trivial changes (1)
  • docs/getting-started/inputs.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/leva/src/types/public.ts
  • .changeset/clear-path-on-unmount.md
  • packages/leva/src/useControls.test.tsx
  • packages/leva/src/utils/input.ts
  • packages/leva/src/utils/data.ts

… feedback

- Export StoreType and Data from leva/plugin entrypoint so plugin-plot
  resolves the same type identity as the sanitize function signature
- Fix plugin-plot import to use leva/plugin instead of bare path alias
- Remove redundant noCachePaths.delete in useMemo (fresh Set each run)
- Replace forEach with for...of in cleanup to avoid implicit-return lint
- Add mount/unmount toggle to ClearPath story so clearPath behavior is
  demonstrable without leaving the story

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CI build cache (key: Linux-pnpm-build-) is never busted, so CI runs
with a compiled leva-plugin.cjs.d.ts that pre-dates the noCache/setNoCache
additions to StoreType. This causes a structural mismatch when plot-plugin
imports StoreType and passes it to createPlugin's sanitize.

Fix: replace the explicit StoreType annotation with a structural inline
type { get: (path: string) => any } — only the .get method is used in
sanitize, so any version of StoreType satisfies it. Also revert the
unnecessary StoreType/Data re-export from leva/plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The build cache key used hashFiles('/packages/**/*') with an absolute
path — the leading slash makes it resolve to the runner root, not the
repo, so it always produced the same (empty) hash. The cache therefore
never busted, leaving CI with a compiled leva-plugin.cjs.d.ts that
pre-dates the noCache/setNoCache additions to StoreType.

Fix the glob to hashFiles('packages/**/*.ts', 'packages/**/*.tsx') so
the key changes whenever TypeScript sources change.

Also restore the explicit store: StoreType annotation in plot-plugin.ts
that was incorrectly weakened in the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant