JSFiddle - React, Tailwind, and code Playground

JavaScript

///////////////////////// Async data API for testing //////////////////////////////////
var data = [{id:1,parent:0},{id:2,parent:1},{id:3,parent:0},{id:4,parent:3},{id:5,parent:4}];

function asyncReadRoot() {
    return new Promise(function (accept, reject) {
      setTimeout(function () {
         accept(data.filter(function(e){return e.parent===0}));
      },Math.random()*1000);    
    })
}

function asyncGetByParentId(parentId) {
    return new Promise(function (accept, reject) {
      setTimeout(function () {
         accept(data.filter(function(e){return e.parent===parentId}));
      },Math.random()*1000);    
    })
}

/////////////////////// Recursive Promises ///////////////////////
function read(result, root) {
	return new Promise(function (accept, reject) {
     if(!root) {
	   		asyncReadRoot().then(function (arr) {
          var childrenTasks = [];
          arr.forEach(function (e) {
          	result[e.id] = e;
            result[e.id].children = [];
            childrenTasks.push(read(result[e.id], e.id));
          });
          Promise.all(childrenTasks).then((data) => {
						// result already updated
						accept(result);
          });
        });       
     } else {
			  asyncGetByParentId(root).then(function (children) {

          if(children.length>0) {
          	var childrenTasks = [];
            children.forEach(function (child) {
              child.children = [];
            	result.children.push(child);
              childrenTasks.push(read(child, child.id));
            });
            Promise.all(childrenTasks).then(function (data) {
              // Children already updated, nothing to do except waiting for all results
              accept(result);
            });
          } else {
              accept();
          }
        });
     }
  });
}

var res = {};
read(res).then(function (data) {
	console.log(JSON.stringify(res, null, "  "));
});