xplato / useUndoable

↪ React hook for undo/redo functionality (with batteries included)
MIT License
168 stars 7 forks source link

Integration of useUndoable with reactflow v11 #19

Closed MananDudhwala closed 1 year ago

xplato commented 1 year ago

Looking at their docs, it doesn't appear that there are any breaking changes from v10 that would affect useUndoable. Is there some issue you are having?

MananDudhwala commented 1 year ago

since they removed the elements as a mixed component for nodes and edges, on using useUndoable in the following way: const [nodes, setNodes, { undo, canUndo, redo, canRedo, static_setState }] = useUndoable(nodeList);

As the setNodes hook is changed everytime a node is moved, its entire path is getting stored in the past. So when I do undo, instead of directly going from its new position to the original position, the node will retrace it path back in small movements. is there any way I can use the static_setState to avoid this?

geril2207 commented 1 year ago

@MananDudhwala If you want to make setNodes same link you can use this hook for passing setNodes into ref and then calling it from ref

import { useCallback, useRef } from 'react';

export const useEvent = <A extends unknown[], R extends void>(
  fn: (...args: A) => R
) => {
  const ref = useRef(fn);
  ref.current = fn;
  return useCallback((...args: A) => ref.current(...args), [ref]);
};

And then

const setNodes = useEvent(setter)

In this case you always have actual link to setNodes and same link on rerender(except ref which dont change in useCallback deps)

Or maybe you should make some debounce for changing state.

xplato commented 1 year ago

Thanks @geril2207 for adding that! I'm afraid I won't be making too many updates to the React Flow example in this project, since really they are only referentially related. In addition, React Flow changes quite a lot and I won't be keeping up with the state-related changes.

For what it's worth, most of the issues that have been opened in useUndoable (and there are only a handful) are directly related to React Flow, so I'm sure you'll be able to find some documentation on the integration there. If you find any bugs with useUndoable specifically, please open an issue!

(Also, forgive my delay here!!)

DatTN95 commented 3 months ago

since they removed the elements as a mixed component for nodes and edges, on using useUndoable in the following way: const [nodes, setNodes, { undo, canUndo, redo, canRedo, static_setState }] = useUndoable(nodeList);

As the setNodes hook is changed everytime a node is moved, its entire path is getting stored in the past. So when I do undo, instead of directly going from its new position to the original position, the node will retrace it path back in small movements. is there any way I can use the static_setState to avoid this?

I have the same problem, do you have a solution?

MananDudhwala commented 3 months ago

since they removed the elements as a mixed component for nodes and edges, on using useUndoable in the following way: const [nodes, setNodes, { undo, canUndo, redo, canRedo, static_setState }] = useUndoable(nodeList); As the setNodes hook is changed everytime a node is moved, its entire path is getting stored in the past. So when I do undo, instead of directly going from its new position to the original position, the node will retrace it path back in small movements. is there any way I can use the static_setState to avoid this?

I have the same problem, do you have a solution?

I did find a workaround. You can pass the ignoreAction property as true when you don't want to record unnecessary chages, in case of movement, pass the ignore action property true during the onNodeDrag event, and false during the dragStart and dragEnd events. This way it'll only keep the start and end positions and ignore the other movements

DatTN95 commented 3 months ago

@MananDudhwala I tried like you say but i can only move the nodes a little bit. my code const [elements, setElements, { past, undo, canUndo, redo, canRedo }] = useUndoable({ nodes: [], edges: [], });

const [ignoreAction, setIgnoreAction] = useState(false);

const triggerUpdate = useCallback((type, value) => {
    if (!ignoreAction) {
        setElements((prev) => ({
            ...prev,
            [type]: value,
        }));
    }
}, [setElements, ignoreAction]);

const onNodesChange = useCallback((changes) => {
    triggerUpdate('nodes', applyNodeChanges(changes, elements.nodes));
}, [triggerUpdate, elements.nodes]);

const onEdgesChange = useCallback((changes) => {
    triggerUpdate('edges', applyEdgeChanges(changes, elements.edges));
}, [triggerUpdate, elements.edges]);

const onNodeDragStart = (event, node) => {
    console.log('start');
    setIgnoreAction(false);
};

const onNodeDragStop = (event, node) => {
    console.log('stop');
    setIgnoreAction(false);
};

const onNodeDrag = (event, node) => {
    console.log('on drag');
    setIgnoreAction(true);
};