4

I am just trying to map nested values inside of a state object. The data structure looks like so:

enter image description here

I want to map each milestone name and then all tasks inside of that milestone. Right now I am trying to do so with nested map functions but I am not sure if I can do this.

The render method looks like so:

render() {
    return(
      <div>
        {Object.keys(this.state.dataGoal).map( key => {
            return <div key={key}>>

                     <header className="header">
                       <h1>{this.state.dataGoal[key].name}</h1>
                     </header>
                     <Wave />

                     <main className="content">
                       <p>{this.state.dataGoal[key].description}</p>

                         {Object.keys(this.state.dataGoal[key].milestones).map( (milestone, innerIndex) => {
                             return <div key={milestone}>
                                      {milestone}
                                      <p>Index: {innerIndex}</p>
                                    </div>
                         })}
                     </main>

                   </div>
        })}
      </div>
    );
  }

I think that I could somehow achieve that result by passing the inner index to this line of code: {Object.keys(this.state.dataGoal[key].milestones) so it would look like: {Object.keys(this.state.dataGoal[key].milestones[innerIndex]).

But I am not sure how to pass the innerIndex up. I have also tried to get the milestone name by {milestone.name} but that doesn't work either. I guess that's because I have to specify the key.

Does anybody have an idea? Or should I map the whole object in a totally different way?

Glad for any help, Jakub

Jakub Kašpar
  • 285
  • 5
  • 6
  • 16

3 Answers3

12

You can use nested maps to map over the milestones and then the tasks array:

 render() {
  return (
    <div>
      {Object.keys(this.state.dataGoal.milestones).map((milestone) => {
        return (
          <div>
            {this.state.dataGoal.milestones[milestone].tasks.map((task, idx) => {
              return (
              //whatever you wish to do with the task item
              )
            })}
          </div>
        )
     })}
    </div>
  )
}
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • @Jakub Kašpar how the hack this worked for you ? because apart of having wrong closure parenthesis this example didn't worked for me :| – mcmwhfy Mar 23 '20 at 06:22
  • 1
    @mcmwhfy I think the issue that you face here is because of returning the nested data without wrapping in div, However if you use the latest version of react, you could simply return an array, i.e the mapped result. anyways, I updated my answer to take care of the wrapping div – Shubham Khatri Mar 23 '20 at 07:09
  • make sense what you said, but I cannot figure out why on my example is not working where object is looking like : `[ { id: 1, name: "Leanne Graham", username: "Bret", email: "Sincere@april.biz", address: { street: "Kulas Light", suite: "Apt. 556", city: "Gwenborough", zipcode: "92998-3874", geo: { lat: "-37.3159", lng: "81.1496" } } ]` – mcmwhfy Mar 23 '20 at 08:37
1

What you want is flatMap. flatMap takes an array and a function that will be applied to each element in the array, which you can use to (for example) access properties inside each object in the array. It then returns a new array with the returned values from its lambda:

function flatMap(arr, lambda) {
  return Array.prototype.concat.apply([], arr.map(lambda))
}

In our case, we don't have an array, we have an object so we can't use flatMap directly. We can convert the object to an array of its properties' values with Object.values and then make a function that accesses the object with the passed key:

function tasksFromDataGoal(key) {
  return flatMap(Object.values(dataGoal[key].milestones), milestone => milestone.tasks)
}

Working example:

function flatMap(arr, lambda) {
  return Array.prototype.concat.apply([], arr.map(lambda))
}

function tasksFromDataGoal(key) {
  return flatMap(Object.values(dataGoal[key].milestones), milestone => milestone.tasks)
}

const dataGoal = { 123: { milestones: { milestone1: { tasks: ['a', 'b'] }, milestone2: { tasks: ['c', 'd'] } } } }

alert(tasksFromDataGoal('123'))

Author of this implementation of flatMap: https://gist.github.com/samgiles/762ee337dff48623e729

fnune
  • 5,256
  • 1
  • 21
  • 35
  • @FaustoNA thanks for your reply. I have never heard about such function, so thanks again. I am just trying to implement this solution but I can't get it right. I put the `dataGoal` and `tasks` constants inside of the first `Object.keys(this.state.dataGoal).map` to get the key value like so: `const newDataGoal = this.state.dataGoal[key].milestones;` and `const tasks = this.flatMap(Object.values(newDataGoal.milestones), milestone => milestone.tasks)`. But I get the `Uncaught TypeError: Cannot convert undefined or null to object`. Am I doing it wrong? Thanks for any help again. :) – Jakub Kašpar Apr 15 '17 at 08:46
  • 1
    Nevermind, the errors might be the following: 1. You're using `this.flatMap`. You should put `flatMap` somewhere else and use it without `this`, `flatMap` is a utility function and has nothing to do with your component directly. 2. If your application does network requests, you might be passing `undefined` to `Object.keys`. Try `if (this.state.dataGoal)` and then apply your logic. – fnune Apr 15 '17 at 09:02
0

Managed to refactor the render method:

  render() {
    return(
      <div>
        {Object.keys(this.state.dataGoal).map( (key, index) => {
          const newDataGoal = this.state.dataGoal[key].milestones;

          return <div key={key}>

                   <header className="header">
                     <h1>{this.state.dataGoal[key].name}</h1>
                   </header>
                   <Wave />

                   <main className="content">
                     <p>{this.state.dataGoal[key].description}</p><br /><br />

                       {Object.keys(this.state.dataGoal[key].milestones).map( (milestoneKey) => {
                         const milestonesData = this.state.dataGoal[key].milestones[milestoneKey];

                         return <div className="milestone-wrap" key={milestoneKey}>
                                  <label className="milestone-label">{milestonesData.name}</label>

                                    {Object.keys(milestonesData.tasks).map( (taskKey) => {
                                      return <div className="task clearfix" key={taskKey}>
                                                  <input
                                                    className="checkbox-rounded"
                                                    name="task"
                                                    type="checkbox"
                                                    checked={milestonesData.tasks[taskKey].done}
                                                    onChange={(e) => this.handleInputChange(e, key, taskKey)} />
                                                  <div className="task-content">
                                                    <p className="task-name">{milestonesData.tasks[taskKey].name}</p>
                                                    <p className="task-date">{milestonesData.tasks[taskKey].finishDate}</p>
                                                  </div>
                                             </div>

                                    })}

                                </div>

                       })}
                   </main>

                 </div>
          })}
      </div>
    );
  }
Jakub Kašpar
  • 285
  • 5
  • 6
  • 16