leva icon indicating copy to clipboard operation
leva copied to clipboard

useControls() hook dependencies do not cause reevaluation of return values

Open rosskevin opened this issue 9 months ago • 3 comments

With the given settings I am passing, at no point does the react hook property dependency cause the shadows value to be updated. I've hacked around and copied the dependency list to addition args, but it made no difference.

// "leva": "^0.10.0"

  const { isTouchDevice } = useDeviceDetection()
  const { shadows } = useControls(
    'Stage',
    {
      shadows: !isTouchDevice,
    },
    [isTouchDevice],
  )

Image

Am I missing something?

rosskevin avatar Feb 11 '25 00:02 rosskevin

Primitive dependencies aren't passed by reference, so the parseArgs has no effect in my case.

e.g.

  const {
    folderName,
    schema,
    folderSettings,
    hookSettings,
    deps
  } = parseArgs(schemaOrFolderName, settingsOrDepsOrSchema, depsOrSettingsOrFolderSettings, depsOrSettings, depsOrUndefined);

console.log('For ', folderName, ' deps are ', deps);

Results in:

Image

Attempting to use an object instead of primitive also does not work.

Perhaps simplifying the method signature or signatures, providing different hook entries makes sense here? React hooks appear to disallow overloaded signatures.

rosskevin avatar Feb 11 '25 22:02 rosskevin

It appears that use of an object for deps is triggering a call to folder() with updated schema, but the UI component does not change, and the return value from useControl does not change.

  const _schema = useDeepMemo(() => {
    depsChanged.current = true;
    const s = typeof schema === 'function' ? schema() : schema;
    console.log('useDeepMemo called for a recalc for ', folderName, ' folderSettings: ', folderSettings, ' schema: ', s);
    return folderName ? {
      [folderName]: folder(s, folderSettings)
    } : s;
  }, deps);

Image

rosskevin avatar Feb 11 '25 22:02 rosskevin

I believe this is by design. The deps will allow you to update the schema when they change, but the return values of the controls are driven by the state of the control in leva.

If you would like to update the value of a control directly you need to follow this pattern using the set function to update the state of the control. When you use a function to initialize your schema, the useControls hook behaves like a useState hook.

const [{entities}, setStats] = useControls('Stats', () => {
    return {
      entities: {
        value: '',
        label: 'entities',
        editable: false,
      },
    };
  });

  useEffect(() => {
    setStats({
      entities: entities.length.toFixed(0),
    });
  }, [entities, setStats]);

thejustinwalsh avatar May 23 '25 02:05 thejustinwalsh

The above is correct, values are not updated via dependencies, only the controls themselves are - removing, adding- etc. @thejustinwalsh has the right solution for this use-case. Please reopen if there's more to it!

gsimone avatar Nov 08 '25 18:11 gsimone