DEV Community

Berra
Berra

Posted on

Recursion recipe

I think recursion is really hard. It’s not like it's really hard. It’s just that I kind of always has to redo my functions many times before I get it right.

So I will try to make a small recipe for doing recursion on a nested data structure.

Say we have this data:

[{
  label: "i am root",
  href: "/",
  children: [{
    label: "i am a leaf",
    value: "/leafnode-1",
    children: null
  }]
}]
Enter fullscreen mode Exit fullscreen mode

And maybe we want to turn this into a navigation or something like this:

<ul>
  <li>
    <a href="/">i am root</a>
    <ul>
      <li><a href="/leafnode-1">i am a leaf</a></li>
    </ul>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

And this is our output functions. First the outer function that will render the <ul> element in our example. It will take a node and a function to render the list.

function renderList(node, innerFn) {
  return `<ul>${innerFn(node).join("")}</ul>`;
}
Enter fullscreen mode Exit fullscreen mode

The inner function (innerFn) will return an array with the output of our next function.

And so. This is the function to render the list. It will take an item as argument and the outer function to render a sublist. And so on.

function renderItems(item, outerFn) {
  return `
    <li><a href="${item.href}">${item.label}</a>
      ${outerFn(item.children)}
    </li>
  `;
}
Enter fullscreen mode Exit fullscreen mode

And this is the "behind the scenes" function that connects the functions and does the mapping.

function createRecursionFunction({ 
  innerFn,
  outerFn,
  renderLeaf,
  isLeafNode 
}) {
  function _outerFn(node) {
    if (isLeafNode(node)) {
      return renderLeaf(node);
    }
    return outerFn(node, _innerFn);
  }
  function _innerFn(node) {
    return node.map(function mapper(i) {
      return innerFn(i, _outerFn)
    });
  }
  return function(data) {
    return _outerFn(data);
  };
}
Enter fullscreen mode Exit fullscreen mode

Now we can use our factory to create a brand new function. But first let's make a function to check if we’re in a leafnode and render an empty string if that is the case.

function isLeafNode(node) {
  return node === null;
}

function renderLeaf() {
  return '';
}
Enter fullscreen mode Exit fullscreen mode
const createMenu = createRecursionFunction({
  outerFn: renderList,
  innerFn: renderItems,
  renderLeaf: renderLeaf,
  isLeafNode: isLeafNode,
});

const htmlMenu = createMenu(data);
Enter fullscreen mode Exit fullscreen mode

Now we have a flexible recursion function factory that will take your data an inner and an outer function as arguments.

I haven't used this in production myself. But I imagine it could be handy.

Until next time.

Top comments (0)