Treact - Recursive Components in React
React is ideally suited to the task of creating trees, being at its core, a tree of functions that call one another. In this demo we’ll look at creating a tree using a data set containing trees (of the Kingdom Plantae). So the theme is trees.
Here’s the demo that we are working towards.
Flat vs Nested Data
As our subject is trees it’s their taxonomic rank that we’ll be exploring. There are many but we'll be using only a subset:
1. Kingdom
2. Order
3. Family
4. Genus
5. Species (or Common Name)
So an ash tree would come out as
1. Plantae
2. Lamiales
3. Oleaceae
4. Fraxinus
5. Fraxinus excelsior (Ash)
One common way to represent tree data is to nest the containing information.
[
{
"taxon": "Kingdom",
"value": "Plantae",
"children": [
{
"taxon": "Order",
"value": "Lamiales",
"children": [
{
"taxon": "Family",
"value": "Oleaceae",
"children": [
{
"taxon": "Genus",
"value": "Fraxinus",
"children": [
{
"taxon": "Species",
"value": "Fraxinus excelsior",
"children": []
}
]
}
]
}
]
}
]
}
]
Whilst this seems a natural fit it has many disadvantages.
- 1. The shape of the data is hard to work with, finding a leaf node (a tree) requires recursion.
- 2. Each datum has no idea of its context, eg a species only knows its genus by knowing its parent.
- 3. The hierarchy is very rigid, if we only want to look at everything beneath families we still need to go through the root Plantae.
A more flexible approach is to keep our data flat. In this instance we already know the levels of our tree (the taxonomic ranks we’ll be using), this might not be true for other trees. A family tree for example could span any number of generations. Here however our levels are known to us in advance (those being Kingdom, Order, Family, Genus, Species)
As such the Ash tree can be easily represented:
{
"Kingdom": "Plantae",
"Order": "Lamiales",
"Family": "Oleaceae",
"Genus": "Fraxinus",
"Species": "Fraxinus excelsior"
}
Certainly much easier to grok from my point of view and I’ll demonstrate further advantages later on. With our data model agreed upon (well I’m happy with it), let’s render it out in React.
A Basic Implementation
We can get a basic implementation working in 50 or so (readable) lines of Typescript.
import { FC } from "react";
import "./App.css";
import allTrees from "./data.json";
import { ReactComponent as Logo } from './logo.svg'
import pkg from '../package.json'
type Taxa = typeof allTrees[number];
type TaxonomicRank = keyof Taxa;
type TaxonomyProps = {
taxa: Array<Taxa>;
rank: TaxonomicRank;
children?: undefined; // Explicitly forbids children
}
// domain, kingdom, phylum, class, order, family, genus, species
const taxonomies: Array<TaxonomicRank> = [
"Kingdom",
"Order",
"Family",
"Genus",
"Name",
];
const Taxonomy: FC<TaxonomyProps> = (props) => {
const { taxa, rank } = props;
// Find the unique taxa in our child taxonomies
// EG unique taxa with a key of "Genus"
const taxaInRank = taxa.reduce<Array<Taxa>>((a, c) => {
if (a.find((v) => v[rank].value === c[rank].value)) {
return a;
}
return [...a, c];
}, []);
return (
<div>
{/* Loop through the taxa in the rank */}
{taxaInRank.map((taxon) => {
// Pull out all the children matching that rank to pass on as a prop
const childrenOfTaxon = taxa.filter(
(t) => t[rank].value === taxon[rank].value
);
const subRank = taxonomies.at(taxonomies.indexOf(rank) + 1);
const hasSubrank = subRank !== undefined;
return (
<details open={hasSubrank}>
<summary>
<a href={taxon[rank].href} target="_blank" rel="noreferrer">
{taxon[rank].value}
</a>
</summary>
{/* Here the element now calls itself */}
{hasSubrank && (
<Taxonomy
rank={subRank}
taxa={childrenOfTaxon}
/>
)}
</details>
);
})}
</div>
);
};
function App() {
return (
<main className="App">
<a href={pkg.repository.url} target="_blank" rel="noreferrer">
<Logo />
</a>
<div>
<Taxonomy
rank={taxonomies[0]}
taxa={allTrees}
/>
</div>
</main>
);
}
export default App;
Here’s the same code on github.
With minimal CSS we already have our tree rendering every level of our taxa up to the tree itself, our leaf node.

Improvements
Because we’ve kept our schema flat by abstracting the keys (in this case taxonomic ranks) we can modify which taxonomic ranks we view. Currently we are recursing through "Kingdom", "Order", "Family", "Genus", "Species”. If we only want to observe the “Species” though, this can easily be achieved by passing it in to our initial Taxonomy.
function App() {
const rank = "Species";
return (
<div className="App">
{/* Our initial call to our Taxonomy */}
<Taxonomy rank={rank}>{allTrees}</Taxonomy>
</div>
);
}
With this in mind, it’s easy to see how rank may be stateful, allowing users to limit the ranks being viewed.
We can also opt to observe different keys of our trees, for instance Species is probably not all that useful to most people who know trees by their common names.
{
"Name": "Ash",
"Species": "Fraxinus excelsior"
// ...
}
Again, this is easily toggled by switching between two different taxa.
const taxaByName: Array<TaxonomicRank> = [
"Kingdom",
"Order",
"Family",
"Genus",
"Name",
];
const taxaBySpecies: Array<TaxonomicRank> = [
"Kingdom",
"Order",
"Family",
"Genus",
"Species",
];
The complete source code for this project can be found in the repo.
Summary
Even when everything in your repo is screaming “TREE!” keeping data flat may prove more flexible. In this instance we knew our groups in advance, where this might not be the case extra keys might indicate relationships between nodes.
[
// ...
{
"id": "LETOTHEJUST",
"fatherId": "THEOLDDUKE",
"name": "Leto Attreides"
},
{
"id": "MUADDIB",
"fatherId": "LETOTHEJUST",
"name": "Paul Attreides"
},
{
"id": "STALIAOFTHEKNIFE",
"fatherId": "LETOTHEJUST",
"name": "Alia Attreides"
}
]
Of course in an ordinary family tree there is more than one parent, I’ll skip over that.
Owing to React's nature Recursive Components are a good use case for the language and implementations that I’ve seen in Vue and Angular aren’t quite as comprehensive.