use-tweaks icon indicating copy to clipboard operation
use-tweaks copied to clipboard

Handling multiple instances of a component with tweaks

Open Pocket-titan opened this issue 3 years ago • 8 comments

Hey! Love the library :)

I have multiple instances of a component with a useTweaks call, say:

const Button = ({ text }: { text: string }) => {
  const { color } = useTweaks("Buttons", { color: "blue" });

  return <button style={{ backgroundColor: color }}> {text} </button>
}

However, every single instance Button will add its own color setting in the tweakpane, whereas my intent was to have them all share the same value. My question: should I just hoist the useTweaks call up one level and pass the tweaked value down as a prop (feels kind of meh)? Or is there a nicer solution to this? I tried passing the same presetKey but they still have different tweakpane inputs. Here's a sandbox demonstrating the "issue": https://codesandbox.io/s/gracious-bas-22vg2?file=/src/App.tsx.

Pocket-titan avatar Nov 05 '20 15:11 Pocket-titan

While yes, to solve this you should hoist up the tweaks and then pass down the props or use context, it might be a common enough case that we would want to solve. Cc @dbismut what do you think?

gsimone avatar Nov 05 '20 16:11 gsimone

If we do this then we should use zustand as you initially thought @gsimone 😂 That's essentially the same as:

const { color } = useStore(s => ({ color: s.color }))

I think it makes a lot of sense.

EDIT: I think we would need to prevent unnecessary renders of components that don't subscribe to the whole state. Not sure zustand is the right one here, maybe jotai but that would mean having a provider...

dbismut avatar Nov 05 '20 16:11 dbismut

@Pocket-titan It's harder than I thought. It needs to keep track of all panes and nested panes added by components and remove them only when none is being used. I gave it a shot today on the zustand branch here https://github.com/pmndrs/use-tweaks/tree/zustand.

I'm not proud of this: it is bug prone and really feels like we're fighting Tweakpane and React at the same time. that's terrible

dbismut avatar Nov 07 '20 18:11 dbismut

I see! Now that you mention it, another difficulty comes to mind:

  • Say I want some shared tweaks, but also a unique one for deleting a specific instance. So:
const { scale } = useTweaks("Suzanne", {
  ...makeFolder("Scale", {
    scale: { value: 1, max: 3 },
  }),
  // Can't do this, only the button of the last instance rendered will show up (due to key equality things?)!
  ...makeButton(`Delete number ${id}`, () => {
    remove();
  })
});

// Have to add this "unique" tweak to its own folder to make it work
useTweaks(`Suzanne number ${id}`, {
  ...makeButton("Remove", () => {
    remove();
  }),
})

where id is some kind of unique instance identifier. Feels like this is more of a niche edge-case though.

At first I thought that maybe WeakMap could help but you're probably right that fighting both React and Tweakpane will cause more issues to pop up along the way.

Here's an idea: what if we check for the presetKey option in the schema, and use only that (no nested folder/key structure) to determine equality? Meaning that if I want to have a tweak that's shared across instances I can pass presetKey: "shared" & it won't create multiple tweaks apart from the first one.

(Tbf: this would still require the use of zustand, but maybe it would be easier b/c this way you don't have to deal with the nesting? but using presetKey like this is maybe confusing, since in Tweakpane you use it to avoid name collisions, and this way you would sort of force the name collision to occur. difficult!)

Pocket-titan avatar Nov 09 '20 12:11 Pocket-titan

Hi @Pocket-titan we got pumped and decided to rewrite something from scratch, that fits React and supports your idea out of the box with no major hassle.

useTweaks({ color: 1, number: 4 }, folder("sub", { a: 2, b: 3 }, folder("sub2", { c: 4 })))

You'll also be able to do:

useTweaks("sub.sub2", { c: 4 })

And get the value. Of course the value will only be initialized once (by the first hooks that gets called).

We'll try to release something in alpha as soon as it's usable.

PS: the idea is to store the data structure but flatten. So that the store looks like:

{
  "valueKey": value
  "folder.subfolder.valueKey": value
  // ...
}

dbismut avatar Nov 10 '20 12:11 dbismut

If anybody comes across this and wants to try alphas of the new version, drop me a DM on twitter or an email!

gsimone avatar Nov 10 '20 12:11 gsimone

@gsimone what ended up happening with this?

joeyfigaro avatar Mar 03 '23 22:03 joeyfigaro

@joeyfigaro They are currently working on another library leva. This repository was transferred from them to the official Tweakpane organization but there is no active maintainer at this time. I want to handle it but I'm busy updating the core...

cocopon avatar Mar 04 '23 01:03 cocopon