An immutable tree structure because it's very difficult to correctly handle tree-like data in React

An immutable tree structure because it's very difficult to correctly handle tree-like data in React

An immutable tree structure because it's very difficult to correctly handle tree-like data in React. Despite the name, this can be used as a standalone library, or even (theoretically) with other JS frameworks. It's just called this because immutable-tree was already taken on npm (whoops!)

react-immutable-tree

An immutable tree structure because it's very difficult to correctly handle tree-like data in React. Despite the name, this can be used as a standalone library, or even (theoretically) with other JS frameworks. It's just called this because immutable-tree was already taken on npm (whoops!)

An ImmutableTree is a tree structure that can have any number of ordered children. (Note: it's not a good fit for binary tree data where right/left child relationships matter, because deleting the first child in a node shifts the second child to the first position.) It is a subclass of EventTarget, which makes it easy to subscribe to changes.

When a node changes, its ancestors are all replaced, but its siblings and children are not (the siblings and children's .parent properties change, but it's the same object). Given the following graph (adapted from here), updating the purple node causes that node, as well as all green nodes, to be replaced. Orange nodes are reused.

Image representing upward propagation of changes

That makes this library compatible with things like React.memo(), which use a simple equality check to decide whether to re-render. Simply subscribe to changes to the tree and grab the new root object when those changes occur.

Getting started

Constructing your tree the easy way

...that is, from JSON data. ImmutableTree provides a helper method, deserialize, which will construct a tree out of your tree-like data. Just provide:

  1. The object representing your root node
  2. A function that, given an object representing a node, returns { data, children } (the data object you want associated with the node in the ImmutableTree, and an array of JSON data objects representing children yet to be parsed). This function is not required if your data is already in { data, children } form (which is the default output of tree.serialize()).
import { ImmutableTree } from 'react-immutable-tree';

// Deserializer to convert my JSON data to { data, children } format
// let's say our data looks like { firstName: 'John', lastName: 'Doe', friends: [ ...more people... ] }
function jsonDataToTree(jsonNode) {
  return {
    data: { firstName: jsonNode.firstName, lastName: jsonNode.lastName },
    children: jsonNode.friends,
  }
}
const myTree = ImmutableTree.deserialize(JSON.parse(" (data) "), jsonDataToTree);

// ...do things with the tree...

// Serializer to turn treeNode data back into our JSON format
// This function is unnecessary if our preferred format is { children, data }
function treeNodeToJsonData(data, children) {
  return {
    firstName: data.firstName,
    lastName: data.lastName,
    friends: children,
  };
}
const serialized = myTree.serialize(treeNodeToJsonData);

Using in a React app

const NodeView = React.memo(({ node }) => (
  <li>
    {node.data.counter}
    <Button onClick={() => node.remove()}>Delete this node</Button>
    <Button onClick={() => node.setData({ counter: 0 })}>Reset this node</Button>
    <Button onClick={() => node.updateData(oldData => ({ counter: oldData.counter + 1 }))}>Increment this node</Button>
    <ul>
      {node.children.map(child => (
        <NodeView node={child} key={child.data.id}/>
      ))}
    </ul>
  </li>
));

import { useTree } from 'react-immutable-tree/hook';
const App = ({tree}) => {
  const rootNode = useTree(tree);

  return (
    <ul>
      <NodeView node={rootNode}/>
    </ul>
  );
};

ReactDOM.render(<App tree={myTree} />, document.getElementById('app'));

Constructing/modifying your tree the harder way (manually)

import { ImmutableTree } from 'react-immutable-tree';

const tableOfContents = new ImmutableTree();
tableOfContents.addRootWithData({ title: null });

// If we don't need the tree to behave immutably yet, the easiest way to build it is using this function
const root = tableOfContents.root;
root.dangerouslyMutablyInsertChildWithData({ title: '1\. How I did it' });
root.dangerouslyMutablyInsertChildWithData({ title: '3\. Why I did it' });
root.dangerouslyMutablyInsertChildWithData({ title: '2\. If I did it' }, 1); // optional second argument is index

// That's because most other functions cause the nodes to replace themselves, dispatch events, etc.
// And since they behave that way, we often have to walk the entire tree for subsequent operations

tableOfContents.root.children[0].insertChildWithData({ title: '1.1\. How' });
tableOfContents.root.children[0].insertChildWithData({ title: '1.3\. did' }); // root is a different object now!
tableOfContents.root.children[0].insertChildWithData({ title: '1.2\. I' }, 1); // again, optional second argument is index
tableOfContents.root.children[0].children[2].remove(2);

// HOWEVER, many functions return the updated version of the node you're operating on, making it easy to keep working with the same node
let myNode = tableOfContents.root.children[0];
myNode = myNode.updateData(oldData => { ...oldData, title: 'my very exciting title' });
myNode = myNode.insertChildWithData({ title: 'my even more exciting title' });
myNode = myNode.moveTo(someOtherNode, 2); // No new node is generated for this one, it returns itself. Also, optional second arg is index
myNode = myNode.remove(); // Same here

API Refernce

Check out the Docs!

Running tests

Running the test suite requires Node 15 because Node only recently added EventTarget and I don't want to polyfill it just for the tests.

Download Details:

Author: mrjacobbloom

Source Code: https://github.com/mrjacobbloom/react-immutable-tree

react reactjs javascript

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How native is React Native? | React Native vs Native App Development

Article covers: How native is react native?, React Native vs (Ionic, Cordova), Similarities and difference between React Native and Native App Development.

Increase Performance of React Applications Via Array JavaScript Methods

Increase Performance of React Applications Via Array JavaScript Methods. We will create a simple event management application in the react to add, update, and delete an event.

Routing in React without React-Router

I have been using React JS in my projects for quite some time now and am used to managing routing in my app using the react-router package. I have always been keen on having as little dependencies in my apps as possible, so, I always felt perturbed by the use of this particular package in simpler apps which did not have complex routes.

The Ugly Side of React Hooks

In this post, I will share my own point of view about React Hooks, and as the title of this post implies, I am not a big fan.

ReactJS: The Javascript Developer’s Guide

This article will walk you through the concepts you would need to know to step into the world of widely used ReactJS.