-1

I am using solid-record to build a mind mapping application. The data in the application is a tree of nodes, and I want the ability to reparent a node, with undo/redo. The issue I am facing is that even though the undoing operation works fine, the redoing one does not.

I have recreated my issue in a simple application:

import { Component, For } from 'solid-js'
import { createStore } from 'solid-js/store'
import { createHistory } from 'solid-record'

type Node = {
  id: number,
  children: Array<number>
  parentid: number
}

const initialValue = [
  { id: 0, children: [1, 2, 3], parentid: -1 },
  { id: 1, children: [], parentid: 0 },
  { id: 2, children: [], parentid: 0 },
  { id: 3, children: [], parentid: 0 },
]

const [state, setState] = createStore(initialValue)
const undoHistory = createHistory()

const changeState = () => {
  undoHistory.batch()

  const nodeid = 3
  const oldparentid = 0
  const newparentid = 2
  let node = state[nodeid]
  let oldparent = state[oldparentid]
  let newparent = state[newparentid]

  // first remove node form parent's children
  undoHistory.add(setState, n => n.id === node.parentid, 'children', oldparent.children.filter(n => n !== node.id))
  // then add to new parent's children
  undoHistory.add(setState, n => n.id === newparent.id, 'children', [...newparent.children, node.id])
  // lastly, point to new parent
  undoHistory.add(setState, n => n.id === node.id, 'parentid', newparent.id)
  undoHistory.unbatch()
}

const App: Component = () => {
  return (
    <>
      <For each={state}>{(node: Node) => <div>{`id: ${node.id}, parentid: ${node.parentid}, children: ${node.children}`}</div>}</For>
      <button onClick={changeState}>Change Parent of 3</button>
      <button onClick={() => undoHistory.undo()} disabled={!undoHistory.isUndoable()}>Undo</button>
      <button onClick={() => undoHistory.redo()} disabled={!undoHistory.isRedoable()}>Redo</button>
    </>
  );
};

export default App;

When clicking the 'Change parent of 3' button, the changeState function:

  1. removes node 3 from its parent (node 0) list of children
  2. adds node 3 to its new parent (node 2) list of children
  3. resets the parent of node 3 to 2

Redo properly restores the state to its initial value with 1,2,3 listed as children of 0.

But Redo sets the list of node 0's children to 3 where it should set it to 1,2! VERY strangely, this does not happen if I don't set the parentis property of node 3, which has no direct relation to the children property of node 0...

I suspect this is a reference vs value kind of problem, or it may be a bug in solid-record ... Any help?

Franck Dervaux
  • 121
  • 2
  • 12

1 Answers1

0

As I suspected, the issue is a reference one ... When doing the first operation, the proper element of the array is selected with a 'search' function n => n.id === node.parentid. But the way solid-record stores the history of command makes it so that it only stores the 'node' object. And the parentid property of that same object is modified by the last operation. The problem is solved by using a local variable storing the node.parentid.

Or, after better reading the SolidJS store API documentation, by directly using the id instead of a search function, like:

undoHistory.add(setState, node.parentid, 'children', oldparent.children.filter(n => n !== node.id))
Franck Dervaux
  • 121
  • 2
  • 12