Skip to content

Commit a1f6056

Browse files
RodrigoHamuyclaude
andcommitted
feat: add LevaRootProps.clearOnUnmount to clear all inputs on unmount
- 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>
1 parent 333fead commit a1f6056

5 files changed

Lines changed: 38 additions & 3 deletions

File tree

packages/leva/src/components/Leva/LevaRoot.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo, useState } from 'react'
1+
import React, { useEffect, useMemo, useState } from 'react'
22
import * as RadixTooltip from '@radix-ui/react-tooltip'
33
import { buildTree } from './tree'
44
import { TreeWrapper } from '../Folder'
@@ -90,10 +90,20 @@ export type LevaRootProps = {
9090
* If true, the copy button will be hidden
9191
*/
9292
hideCopyButton?: boolean
93+
/**
94+
* If true, all inputs will be cleared from the store when their component unmounts,
95+
* regardless of the per-input clearOnUnmount setting.
96+
*/
97+
clearOnUnmount?: boolean
9398
}
9499

95-
export function LevaRoot({ store, hidden = false, theme, collapsed = false, ...props }: LevaRootProps) {
100+
export function LevaRoot({ store, hidden = false, theme, collapsed = false, clearOnUnmount = false, ...props }: LevaRootProps) {
96101
const themeContext = useDeepMemo(() => mergeTheme(theme), [theme])
102+
103+
useEffect(() => {
104+
if (store) store.setClearOnUnmount(clearOnUnmount)
105+
}, [store, clearOnUnmount])
106+
97107
// collapsible
98108
const [toggled, setToggle] = useState(!collapsed)
99109

packages/leva/src/store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export const Store = function (this: StoreType) {
1313

1414
this.storeId = getUid()
1515
this.useStore = store
16+
this.clearOnUnmount = false
17+
this.setClearOnUnmount = (flag) => {
18+
this.clearOnUnmount = flag
19+
}
1620
/**
1721
* Folders will hold the folder settings for the pane.
1822
* @note possibly make this reactive

packages/leva/src/types/internal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type Dispose = () => void
2929
export type StoreType = {
3030
useStore: UseBoundStore<StoreApi<State>> & SubscribeWithSelectorAPI<State>
3131
storeId: string
32+
clearOnUnmount: boolean
33+
setClearOnUnmount: (flag: boolean) => void
3234
orderPaths: (paths: string[]) => string[]
3335
setOrderedPaths: (newPaths: string[]) => void
3436
disposePaths: (paths: string[]) => void

packages/leva/src/useControls.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { levaStore } from './store'
2626

2727
afterEach(() => {
2828
levaStore.dispose()
29+
levaStore.setClearOnUnmount(false)
2930
})
3031

3132
function NumberComponent({ id }: { id?: string }) {
@@ -123,6 +124,23 @@ describe('useControls mount/unmount lifecycle', () => {
123124
expect(getByTestId2('value2').textContent).toBe('5')
124125
})
125126

127+
it('store-level clearOnUnmount resets all inputs on remount', () => {
128+
levaStore.setClearOnUnmount(true)
129+
130+
const { getByTestId, unmount } = render(<NumberComponent id="value" />)
131+
expect(getByTestId('value').textContent).toBe('5')
132+
133+
act(() => {
134+
levaStore.setValueAtPath('myNumber', 42, true)
135+
})
136+
expect(getByTestId('value').textContent).toBe('42')
137+
138+
act(() => unmount())
139+
140+
const { getByTestId: getByTestId2 } = render(<NumberComponent id="value2" />)
141+
expect(getByTestId2('value2').textContent).toBe('5')
142+
})
143+
126144
it('resets to the initial value when remounted after clearPath', () => {
127145
const { getByTestId, unmount } = render(<NumberComponent id="value" />)
128146
expect(getByTestId('value').textContent).toBe('5')

packages/leva/src/useControls.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ export function useControls<S extends Schema, F extends SchemaOrFn<S> | string,
207207
depsChanged.current = false
208208
return () => {
209209
store.disposePaths(paths)
210-
clearOnUnmountPaths.forEach((path) => store.clearPath(path))
210+
const pathsToClear = store.clearOnUnmount ? paths : [...clearOnUnmountPaths]
211+
pathsToClear.forEach((path) => store.clearPath(path))
211212
}
212213
}, [store, paths, initialData, clearOnUnmountPaths])
213214

0 commit comments

Comments
 (0)