4

I have a immutable nested tree (mori, immutable-js et al) consisting of arbitrary nodes, think file browser. The tree gets rendered by React. If a component representing a node receives focus, I'd like to:

  • Show an input field on the node component to change e.g. the node name.
  • Show a global panel that contains UI components to edit additional properties of the currently focused node, depending on the type of the selected node.

A simplistic state object could look like this:

{
    root: {
        type: 'folder'
        name: 'Root',
        focused: false,
        children: [
            {
                type: 'text',
                name: 'Essay',
                focused: false
            },
            {
                type: 'folder',
                name: 'Images',
                focused: false,
                children: [
                    {
                        type: 'image',
                        name: 'Paris',
                        focused: true
                    }
                ]
            }
        ]
    },
    currentlyFocusedNode: <Reference to Paris node>
}

Now, how would I keep a valid reference to the currently focused node? If I stored a Paris node reference at currentlyFocusedNode, it would be out of sync as soon as any other part of the app modifies the node (e.g. the inline name input from above). I thought about storing the path to the focused node, which I could use to retrieve the current reference:

currentlyFocusedNode: ['root', 'children', 0, 'children', 0]

But this seems very shaky to me as well, because even a simple node move operation on the tree could leave this path pointing to the wrong or even a non-existing node.

How are those kind of things handled with immutable data structures? Or am I not thinking "immutable" enough at all?

Lukas Bünger
  • 4,257
  • 2
  • 30
  • 26
  • If I understand correctly, you don't need a separate reference. Update the data, and the various views will rerender. All the logic is in creating the data object, passing what needs to be focused next is the challenge. You might need to add a unique ID to each node, so you can pass that do the update. – Shawn Dec 11 '14 at 05:14
  • Thanks for your reply, Shawn. If I don't need a separate reference or pointer, how is my global property panel supposed to know, which node it is representing? I could in fact assign UID's but wouldn't this lead to a whole lot of iterating over the tree? – Lukas Bünger Dec 11 '14 at 08:38
  • I'm not sure iterating over the tree is avoidable. If there's a chance you'll be dealing with thousands of nodes, then yes, my suggestion might be a problem. – Shawn Dec 11 '14 at 18:04
  • You could make a copy of the focused node for your global panel to use. You just need to update that copy whenever the main tree is updated. – Shawn Dec 11 '14 at 18:05

3 Answers3

3

Your question does not have an answer as asked. And it seems you had a sense of that when you said

Or am I not thinking "immutable" enough at all?

First you say you are using an immutable data structure. Meaning it can't be changed.
Then you say:

If I stored a Paris node reference at currentlyFocusedNode, it would be out of sync as soon as any other part of the app modifies the node

With immutable data structures things don't get modified. What you have is an old version of the data and a new version of the data. Neither will ever change.

So the question should really be something like:

How do I identify when 2 nodes represent the same data?

Answer is use an ID.

Another good question might be:

Given a reference to an old node, tree, and newData, how can I update the tree and keep track of the node?

This depends largely on how the rest of the code works and what parts you have access to. E.g. you could have something like this:

function updateNodeInTree(nodeId, newData, inTree){
  oldNode = findNode(nodeId, inTree);
  newNode = merge(oldNode, newData);
  newTree = merge(inTree, {path: {to: newNode}});
  return [newTree, newNode]; 
}

But if you don't have access to where the tree is updated, you may have to settle for:

newTree = updateTree(oldTree, someData);
newNode = findNode(nodeId, newTree);
Mark Bolusmjak
  • 23,606
  • 10
  • 74
  • 129
1

Give each node a unique ID. Maintain a focused_id value.

Any component rendering parts of a node can check if
this.props.node.id === this.props.focused_id
and render appropriately.

Mark Bolusmjak
  • 23,606
  • 10
  • 74
  • 129
  • Thanks for your answer! Very true if a component manages only one node throughout its lifetime. If not -> iterating, as Shawn pointed out above. – Lukas Bünger Dec 12 '14 at 10:28
0

This is where Flux architecture pattern comes in.

I would handle it like this:

  1. Save the root and currentlyFocusedNode in a TreeStore where currentlyFocusedNode either saves null or references an object in the tree.
  2. When a node is clicked, an event is dispatched (using AppDisptacher) with eventType: 'nodeClicked' and payload containing the object (reference) which was clicked.
  3. TreeStore listens to AppDispatcher and makes changes to currentlyFocusedNode
  4. React component listens to changes in TreeStore and re-renders whenever there is a change.
Sanket Sahu
  • 8,468
  • 10
  • 51
  • 65
  • If it is to be saved in the database then reference using __ID__ would be a better option than saving JavaScript references. – Sanket Sahu Dec 13 '14 at 08:08
  • 2
    Thanks for your reply! As a matter of fact, an ID is the only valid option of the two as a reference in an Immutable data structure will be out of sync as soon as any other part of the app modifies the referenced node. This is actually a core concept of Immutable data structures. And while the Flux pattern is certainly one way of establishing a sensible data flow for the above example, the selection of the pattern is not the topic of this thread. – Lukas Bünger Dec 13 '14 at 14:23