Skip to content

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,
      },
    ],
  },
];

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>

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>`;
}

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>
  `;
}

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);
  };
}

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 "";
}
const createMenu = createRecursionFunction({
  outerFn: renderList,
  innerFn: renderItems,
  renderLeaf: renderLeaf,
  isLeafNode: isLeafNode,
});

const htmlMenu = createMenu(data);

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.