What is the most efficient way of rendering a n-level nested object in React JS?

Viewed 1119

I have a data coming from API as follows -

{
  "expression": [
    "a",
    "b"
  ],
  "groups": [
    {
      "expression": [

      ],
      "groups": [
        {
          "expression": [
            "c",
            "d"
          ],
          groups: null
        }
      ]
    },
    {
      "expression": [
        "p",

      ],
      groups: null
    }
  ]
}

I have to render Expressions and Groups (that includes expressions and multiple groups as well). The building block is an expression. We need to display nested expressions as groups.

I tried with recursive function call, but that is not working with much efficiency for an object with large depth.

Here is something similar to my trial -

let conditionStack = [];
  function parseInnerConditions(conditionObj) {
    if (conditionObj.expression) {
      conditionObj.expression.map((each, index) => {
        conditionStack = [
          ...conditionStack,
          <NewExpression key={index} conditionExpression={each} />, //renders an expression
        ];
      });
    }
    if (conditionObj.groups && conditionObj.groups.length) {
      return conditionObj.groups.map((child, index) => {
        conditionStack = [
          ...conditionStack,
          <ConditionGroup key={index}> // renders a block or group of expressions
            {parseInnerConditions(child)}
          </ConditionGroup>,
        ];
      });
    }
    return conditionStack;
  }

  return (
    <div>
    parseInnerConditions(conditionObject)
    </div>
  )

Thanks in advance.

2 Answers

ReactJS is a Component-Based library for generating UIs. So thinking in terms of Components your data is a Group which in turn will contain an expression and other groups like itself. Breaking this down we can form three components:

  1. Expression For simply rendering the expression in a group.
  2. Group For rendering a group.
  3. Groups For extracting and then rendering Group component for further groups in your data.

Here is a sample code:

Index.js This is the file getting data from API.

import React from "react";
import Group from "./Group";

const data = {
  expression: ["a", "b"],
  groups: [
    {
      expression: [],
      groups: [{ expression: ["c", "d"], groups: null }]
    },
    {
      expression: [],
      groups: [
        { expression: ["e", "f"], groups: null },
        { expression: ["g", "h"], groups: null }
      ]
    },
    {
      expression: ["p"],
      groups: null
    }
  ]
};
const stage = 1;
const Main = () => {
  return (
    <div>
      <Group data={data} stage={stage} />
    </div>
  );
};

export default Main;

Group.js For rendering a group.

import React from "react";
import Expression from "./Expressions";
import Groups from "./Groups";

const populateGroup = (data, stage) => {
  const HTML = [];
  for (const x in data) {
    if (x === "expression") {
      HTML.push(<Expression expression={data[x]} />);
    }
    if (x === "groups") {
      HTML.push(<Groups data={data[x]} stage={stage} />);
    }
  }
  return HTML;
};

const Group = ({ data, stage }) => {
  return (
    <div>
      <p>{`Group @ Stage ${stage}`}</p>
      <ol className="Group">{populateGroup(data, stage + 1).map(el => el)}</ol>
    </div>
  );
};

export default Group;

Expression.js For simply rendering the expression in a group.

import React from "react";

const Expression = ({ expression }) => {

return (
    <div className="expression">
      {expression.map(term => (
        <h3 style={{ display: "inline" }}>{term}</h3>
      ))}
    </div>
  );
};

export default Expression;

Groups.js For extracting and then rendering Group component.

import React from "react";
import Group from "./Group";

const Groups = ({ data, stage }) => {

  if (data === null) return <span></span>;

  return (
    <div className="GroupsSection">
      {data.map((section, i) => (
        <Group key={i} data={section} stage={stage} />
      ))}
    </div>
  );
};

export default Groups;

Performance With this Component-Based Approach the data is being chunked which should improve the performance for even better results the for...in and map can be replaced with for loop. Sample Render

Your map() callback doesn't have a return value, yet within the second if statement, you're returning the array, so you'll end up with [undefined, undefined, ...] when you recurse to each groups.

To fix your solution, change both your .map() to .forEach() and remove the return keyword before conditionObj.groups.map(...).

Here's a better approach though:

function ConditionGroup ({ expression, groups }) {
  const conditionExpressions = useMemo(
    () => expression && expression.map(
      (value, index) => (
        <NewExpression key={index} conditionExpression={value} />
      )
    ),
    [expression]
  );
  const conditionGroups = useMemo(
    () => groups && groups.map(
      (value, index) => (
        <ConditionGroup key={index} {...value} />
      )
    ),
    [groups]
  );

  return (
    <div>
      {conditionExpressions}
      {conditionGroups}
    </div>
  );
}

Note that using index as the key is an antipattern. Ideally, you'll want to use a unique id if you're provided one. See ReactJS : What is the best way to give keys in array element for more information on this.

Related