use-tweaks
use-tweaks copied to clipboard
Handling multiple instances of a component with tweaks
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.
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?
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...
@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.
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!)
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
// ...
}
If anybody comes across this and wants to try alphas of the new version, drop me a DM on twitter or an email!
@gsimone what ended up happening with this?