Passing an accumulator to React Children...who gets the state?

I have a React project that uses two dates to calculate a total duration of a project in weeks.
I have an algorithm that divides up that time into 4 different phases.
Each of those phases is a certain number of weeks and the total weeks adds up to the total project duration, and this is returned as an object in my top level component

Example

[
 {mesocycle: 'Foundation', weeks: 5},
 {mesocycle: 'Preparation', weeks: 6},
 {mesocycle: 'Specialization', weeks: 6},
 {mesocycle: 'Race', weeks: 5}
]

I then use .map to cycle through this array of 4 objects, and create a child component.
That child component expands (.push) into a new array of weeks that is the number of weeks in that cycle.

THe output ends up looking like this:

Foundation: 5 weeks
Week 1 of 5
Week 2 of 5
Week 3 of 5
Week 4 of 5
Week 5 of 5
Preparation: 6 weeks
Week 1 of 6
Week 2 of 6
Week 3 of 6
Week 4 of 6
Week 5 of 6
Week 6 of 6
etc

But what I really want is this:

Foundation: 5 weeks
Week 1 of 22
Week 2 of 22
Week 3 of 22
Week 4 of 22
Week 5 of 22
Preparation: 6 weeks
Week 6 of 22
Week 7 of 22
Week 8 of 22
Week 9 of 22
Week 10 of 22
Week 11 of 22

I’m having a hard time figure out how to make my child component return these new numbers that accumulate. Specifically, how to I make the 2nd child (ie Preparation) know that the prior child had 5 weeks and add that to each new week?
I’m using .map so I can’t just easily look at the last object in the array, I think I need to use an accumulator instead?

Parent Component (Mesocycles ):

    const renderMesocycles = atp.map((cycle) =>
      (
      <div>
        <h3>{cycle.mesocycle}: {cycle.weeks} weeks</h3>
        <Microcycles cycle={cycle} />
    </div>
    )
    );

Child Component (Microcycles):

let weeks = []
  function thisCycle (cycle) {
      let cycleLength = cycle.weeks
      for (var i=0; i< cycleLength; i++) {
        weeks.push(<li key={i}>Week {i+1} of {cycleLength}</li>)
      }
      return weeks;
    }

    weeks = thisCycle(this.props.cycle)

    return (
      <div>
        <ul>{weeks}</ul>
      </div>
    );
  }

Maybe I just need to take a break and take a step back. Maybe writing this out will help me too!

Any suggestions?

So you want the child to know its own cycle values, but also the total value of all the nested object’s weeks property? You might want to calculate that total in advance using something like

const totalWeeks = apt.reduce( functionToTotalWeeks );

and then pass that totalWeeks into each Microcycles component. Without seeing the entire code, I’m sort of spitballing here, but that’s what I might do.

The total value is easy, I’m actually passing that in as a prop. But each cycles weeks are calculated within the parent and I’d need cycles 1+2 for cycle 3 and cycles 1+2+3 for cycle 4.

I think I’m juts having a brain fart with arrow functions /ES6 and react components really…I’m not sure why this is difficult for me!

Given the code you’ve shown (which you call the variable atp?), I might consider modifying your data a bit. You want each cycle to look a bit different:

[
  { mesocycle: 'Foundation', weeks: 5, start: 1 },
  { mesocycle: 'Preparation', weeks: 6, start: 6 },
  { mesocycle: 'Specialization', weeks: 6, start: 12 },
  { mesocycle: 'Race', weeks: 5, start: 18 }
]

Doing that, you can cycle weeks times, but use start as the increment offset for each record. Now, the question becomes, how can you add that start property to each object? Array.prototype.reduce() might be handy here:

  • given a starting object of {start: 1, cycles: [] },
  • take each cycle in and
  • return a new object where start is the previous start plus the current cycle’s weeks property, and where the current cycle is added to the cycles property but modified to include a start property equal to the previous start value.

To see what I mean, https://replit.com/@TobiasParent/usingReduceToOffsetStart

Again, I’m just sort of soft-shoeing here, purely taking this as a thought experiment. YMMV.

That’s a great idea, and could be a helpful object to have hanging around, as my plan would allow these phases to be manually adjusted by the user once created with the initial algorithm, which is a little bit arbitrary. So I may be able to work that in from the beginning and not have to use the reduce function. I’ll play with this, thank you.

yes, atp stands for annual training plan.

Thanks again.

1 Like

Easy Peasy! Thanks for your suggestion:
I added this like in the initial reduce function where I calculate the duration of each cycle:

let start = 0
.
stuff here
.
if (i>0) {
      start = start + final[i-1].weeks
    }
    final[i] = { mesocycle: mesocycle["name"], weeks: mesocycleCount, start: start }

And I get this!

0: {mesocycle: 'Foundation', weeks: 5, start: 0}
1: {mesocycle: 'Preparation', weeks: 6, start: 5}
2: {mesocycle: 'Specialization', weeks: 6, start: 11}
3: {mesocycle: 'Race', weeks: 5, start: 17}

Thank you!

Are you starting at week 0?

yes apparently I am.

Yahoo!

Training Plan Length: 22 weeks
Foundation: 5 weeks
Week 1 of 22
Week 2 of 22
Week 3 of 22
Week 4 of 22
Week 5 of 22
Preparation: 6 weeks
Week 6 of 22
Week 7 of 22
Week 8 of 22
Week 9 of 22
Week 10 of 22
Week 11 of 22
Specialization: 6 weeks
Week 12 of 22
Week 13 of 22
Week 14 of 22
Week 15 of 22
Week 16 of 22
Week 17 of 22
Race: 5 weeks
2 Likes