useDraggable how to make second argument reactive
I am building a draggable library neodrag v3, writing the solidJS adapter for it.
I want the usage to look like this:
I am expecting that when position() is updated, the entire second argument to useDraggable is recreated, and my logic can then call update cycle on it.
But that does not happen! Here is my internal code:
What could be going wrong here?
useDraggable(draggableRef, [
positionPlugin({ current: position() }),
events({
onDrag({ offset }) {
console.log(1);
setPosition({ x: offset.x, y: offset.y });
},
}),
]);
useDraggable(draggableRef, [
positionPlugin({ current: position() }),
events({
onDrag({ offset }) {
console.log(1);
setPosition({ x: offset.x, y: offset.y });
},
}),
]);
import { createDraggable } from '@neodrag/core';
import { DragEventData, unstable_definePlugin, type Plugin } from '@neodrag/core/plugins';
import { createSignal, onCleanup, onMount, createEffect, untrack } from 'solid-js';
import type { Accessor, Setter } from 'solid-js';
const draggable_factory = createDraggable();
interface DragState extends DragEventData {
isDragging: boolean;
}
const default_drag_state: DragState = {
offset: { x: 0, y: 0 },
rootNode: null as unknown as HTMLElement,
currentNode: null as unknown as HTMLElement,
isDragging: false,
};
// Create the state sync plugin with the provided setter function
const state_sync = unstable_definePlugin<[Setter<DragState>]>({
// Ommitted for brevity
});
type PluginOrFactory = Plugin | (() => Plugin);
function resolve_plugin(pluginOrFactory: PluginOrFactory): Plugin {
return typeof pluginOrFactory === 'function' ? pluginOrFactory() : pluginOrFactory;
}
export function wrapper(draggableFactory: ReturnType<typeof createDraggable>) {
return (
element: Accessor<HTMLElement | SVGElement | null | undefined>,
plugins: PluginOrFactory[] = [],
) => {
const [drag_state, set_drag_state] = createSignal<DragState>(default_drag_state);
let instance: ReturnType<typeof draggableFactory.draggable> | undefined;
const state_sync_plugin = state_sync(set_drag_state);
// Initialize draggable instance
onMount(() => {
const node = element();
if (!node) return;
// Initial plugin resolution
const resolved_plugins = untrack(() => plugins.map(resolve_plugin).concat(state_sync_plugin));
instance = draggableFactory.draggable(node, resolved_plugins);
// Cleanup on unmount
onCleanup(() => {
instance?.destroy();
});
});
// Handle plugin updates
createEffect(() => {
if (!instance) return;
// Resolve all plugins, including reactive ones
const resolved_plugins = plugins.map(resolve_plugin).concat(state_sync_plugin);
instance.update(resolved_plugins);
});
return drag_state;
};
}
export const useDraggable = wrapper(draggable_factory);
// Export necessary types and utilities
export * from '@neodrag/core/plugins';
export const instances = draggable_factory.instances;
import { createDraggable } from '@neodrag/core';
import { DragEventData, unstable_definePlugin, type Plugin } from '@neodrag/core/plugins';
import { createSignal, onCleanup, onMount, createEffect, untrack } from 'solid-js';
import type { Accessor, Setter } from 'solid-js';
const draggable_factory = createDraggable();
interface DragState extends DragEventData {
isDragging: boolean;
}
const default_drag_state: DragState = {
offset: { x: 0, y: 0 },
rootNode: null as unknown as HTMLElement,
currentNode: null as unknown as HTMLElement,
isDragging: false,
};
// Create the state sync plugin with the provided setter function
const state_sync = unstable_definePlugin<[Setter<DragState>]>({
// Ommitted for brevity
});
type PluginOrFactory = Plugin | (() => Plugin);
function resolve_plugin(pluginOrFactory: PluginOrFactory): Plugin {
return typeof pluginOrFactory === 'function' ? pluginOrFactory() : pluginOrFactory;
}
export function wrapper(draggableFactory: ReturnType<typeof createDraggable>) {
return (
element: Accessor<HTMLElement | SVGElement | null | undefined>,
plugins: PluginOrFactory[] = [],
) => {
const [drag_state, set_drag_state] = createSignal<DragState>(default_drag_state);
let instance: ReturnType<typeof draggableFactory.draggable> | undefined;
const state_sync_plugin = state_sync(set_drag_state);
// Initialize draggable instance
onMount(() => {
const node = element();
if (!node) return;
// Initial plugin resolution
const resolved_plugins = untrack(() => plugins.map(resolve_plugin).concat(state_sync_plugin));
instance = draggableFactory.draggable(node, resolved_plugins);
// Cleanup on unmount
onCleanup(() => {
instance?.destroy();
});
});
// Handle plugin updates
createEffect(() => {
if (!instance) return;
// Resolve all plugins, including reactive ones
const resolved_plugins = plugins.map(resolve_plugin).concat(state_sync_plugin);
instance.update(resolved_plugins);
});
return drag_state;
};
}
export const useDraggable = wrapper(draggable_factory);
// Export necessary types and utilities
export * from '@neodrag/core/plugins';
export const instances = draggable_factory.instances;
2 Replies
Turns out I have to make the second argument an accessor at all times. Not happy but can live with it
yeah it has to be an accessor or you have to wrap useDraggable in a createEffect user side
Because when you call position() it becomes a real value and not a signal anymore, since it's not in an effect it doesn't rerun
You can just make position an accessor:
useDraggable(ref, [
positionPlugin({current: position // no ()
...
]);
useDraggable(ref, [
positionPlugin({current: position // no ()
...
]);