wsfuller
wsfuller

Reputation: 1840

React mapping multiple arrays

Sorry for any confusion. To put simply I want to make several titles with unordered lists.

<h3>level_3 key title</h3>
<ul>
  <li>level_4 value</li>
  <li>level_4 value</li>
</ul>

<h3> another level_3 key title</h3>
<ul>
  <li>level_4 value</li>
  <li>level_4 value</li>
</ul> 

Difficult to phrase the problem into a question. Essentially having a really hard time trying to parse through some JSON and display the data correctly. What I would like to do is display level_3.x below as a top level list item and the nested level_4.x as nested list items. To be clear using React

Example:

Repeat for every level_3.x object

Example Data

"level_1":{
  "level_2.1":{
    "level_3.1":{
     "level_4.1":["string","string","string"],
     "level_4.2":["string","string","string"]
    }
    "level_3.2":{
     "level_4.1":["string","string","string"],
     "level_4.2":["string","string","string"]
    }
  }
  "level_2.2":{
     "level_3.1":{
       ... Same as above
    }
     "level_3.2":{
       ... Same as above
    }
  }
}

What I have done since you can only map an array, from my understanding, is try and go through level_3.x and push those to an Array. I can map over that and display the items as expected.

Parent Component

render(){
  return( <Child heading="A" data={resource.data} /> )
}

Child Component

render(){
  let groupObj;
  let groupArray;
  let subListItems;

  if('value' in this.props.data){
    var i;
    for(i = 0; i < Object.keys(groupObj).length; i++){
      groupArray.push(Object.keys(groupObj)[i] + "," + Object.values(groupObj)[i]) 
// Have removed the Object.values because I was trying to make everything flat and pull out the first item. Leaving it in because that was my original direction in the case it was actually the right way to go
    }
  }
  listItems = groupArray.map((item, index)=>(<li key={index}>{item}</li>));

  return({listItems})
}

Thus far I can produce:

console log > [level_3.1,level_3.2]

Which is great, and halfway there. Now where things break down is I can't seem to figure out how to get the level_4.x elements to render.

I tried writing another map function inside the previous:

listItems = groupArray.map((item, index) => (
   <li key={index}>
     {item}
     <ul>
       {subListItems = groupObj.item.map((subItem, index) => ( 
         <li key={index+1}>{subItem}</li>
       ))};
     </ul>
   </li>
 ));

This returns a map of undefined error, however if item is replaced with a key it'll work. groupObj.level_4.1.map() That will return a level_4.1 values:

So either doing this completely wrong, or missing a way to pass in a dynamic value into that groupObj.[needs to be dynamic based on parent].map()

Note, do know that using index as a key value is an anti-pattern just have that in for sake of getting this to work.

Update

Feel like this is 90% there but having one last issue. Took both answers into consideration and the easiest for me to implement and understand what I'm typing at this time is the following:

render(){

  let listItems;
  let name = this.props.heading

  if('level_2' in this.props.data){

    let data = this.props.data.level_2[name];
    console.log('ok we can get to work', data); // <<< Good

    listItems = Object.keys(data).map((propKey) => (
      <ul key={propKey}>
       {propKey} // <<< Good
       {console.log('PROP KEY', propKey)} // <<< Good
         {data[propKey].map((childPropKey) =>{
           <li key={childPropKey}>
             {childPropKey} // <<<<<< PROBLEM, This returns nothing in the DOM
             {console.log(childPropKey)} // <<< Good
           </li>
         })}
     </ul>
   ));

return(<div>{listItems}</div>)
}

So I'm able to console.log(childPropKey) and return expected results. But just trying to return {childPropKey} nothing gets returned

Working ES5

listItems = Object.keys(data).map(function(propKey){
    return <ul key={propKey}>
      {propKey}
        {data[propKey].map(function(childPropKey){
          return <li key={childPropKey}>
            {childPropKey}
          </li>
        })}
    </ul>
  });

Working ES6

listItems = Object.keys(data).map((propKey) => (
  <ul key={propKey}>
    {propKey}
    {data[propKey].map((childPropKey) =>(
      <li key={childPropKey}>
        {childPropKey}                 
      </li>
     ))}
  </ul>
));

Upvotes: 5

Views: 44147

Answers (2)

Przemysław Zalewski
Przemysław Zalewski

Reputation: 3996

You should consider recursion for this kind of problems as it is well suited for tree traversal.

Most people ain't aware that you can treat React components as functions and use them recursively. In order to render different types of nodes, you can use the ternary operator, switch statement or other control flow mechanisms.

To iterate an Object you can use Object.entries to get a list of key-value pairs, which you can then process with regular map.

Please see the following example:

const isLeaf = data => typeof data === 'string'
const isArray = data => Array.isArray(data)

const Tree = ({ data }) =>
  // render a leaf - just a string
  isLeaf(data) ? <li>{data}</li> : 

  // render an array
  isArray(data) ? 
  <ul>
    {data.map(x => <Tree data={x} />)}
  </ul> : 

  // render a tree (an object)
  <ol>
  {
    Object.entries(data).map(([key, value]) =>
      <li>
        <h1>{key}</h1>
        <Tree data={value} />
      </li>
    )
  }
  </ol>

const data = {
  "level_1":{
    "level_2.1":{
      "level_3.1":{
       "level_4.1":["a","b","c"],
       "level_4.2":["string","string","string"]
      },
      "level_3.2":{
       "level_4.1":["string","e","string"],
       "level_4.2":["d","string","f"]
      }
    },
    "level_2.2":{
      "level_3.1":{
       "level_4.1":["string","string","string"],
       "level_4.2":["string","string","string"]
      },
      "level_3.2":{
       "level_4.1":["string","string","string"],
       "level_4.2":["string","string","string"]
      }
    }
  }
}

ReactDOM.render(
  <Tree data={data} />,
  document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Upvotes: 4

Nick Wyman
Nick Wyman

Reputation: 1158

Instead of trying to convert the object into an array, just cut that out and loop directly through the Object.keys array.

render () (
  <ul>
    {Object.keys(this.props.data).map((propKey) => {
      <li key={propKey}>
        {this.props.data[propKey]}

        <ul>            
          {Object.keys(this.props.data[propKey]).map((childPropKey) => {
            <li key={childPropKey}>
              {this.props.data[propKey][childPropKey]}
            </li>
          })}
        </ul>
      </li>
    })}
  </ul>
);

I've made some assumptions on your data structure, so hopefully you can translate it to your implementation.

Upvotes: 6

Related Questions