1

I have a React app with an interface that allows users to select a date and time slot. I have a top level object that maintains the state, which might look like this:

this.state = {
  days: [{ 
    date: '12-13-2022',
    time_slots: [{
        start: '10 am',
        end: '11 am',
        selected: false
      },{
        start: '1 pm',
        end: '3 pm',
        selected: false
      }]
    }, {
    date: '12-14-2022',
    time_slots: [{
       start: '10 am',
       end: '11 am',
       selected: false
     }
  }]
}

When a user clicks on a time slot, I want to update the selected property to true.

So far I have this, but I think I'm mutating the state, which is bad practice.

slotClicked(day_index, slot_index) {
  let state = this.state.days[day_index].time_slots[slot_index].selected = true;
  this.setState({state});
}

How might I update the state in an efficient (in terms of re-rendering) and immutable way?

user1063998
  • 584
  • 6
  • 16
  • 1
    Since you don't have an error, I would recommend that you read the answers to this question https://stackoverflow.com/questions/29537299/react-how-do-i-update-state-item1-on-setstate-with-jsfiddle about nested state types and how other people have implemented the state change. – pastaleg Jun 03 '19 at 11:23
  • ...also consider extracting per-item logic into child component for better maintainability – skyboyer Jun 03 '19 at 11:51

4 Answers4

2

You have to deep clone your array, in opposition to other answers:

slotClicked(day_index, slot_index) {
  // If you prefer you can use lodash method _.cloneDeep()
  const newDays = JSON.parse(JSON.stringify(this.state.days));

  newDays[day_index].time_slots[slot_index].selected = true;
  this.setState({days: newDays});
}

If you do not deep clone your array, the time_slots array will be copied by reference and mutanting it will mutate original array in state.

Mosè Raguzzini
  • 15,399
  • 1
  • 31
  • 43
0

You can make use of Array.map function as,

slotClicked(day_index,slot_index){
        let current_state = this.state;
        this.state.days.map((days,days_index) => {
            if(days_index===day_index){
                // console.log("day",days);
                let newSlot = '';
                days.time_slots.map((time_slot,slots_index)=>{
                    if(slots_index===slot_index){
                        // console.log("time",time_slot);
                        newSlot = Object.assign({},time_slot,{selected:true});
                    }
                })
                // console.log("new slot",newSlot);
                days.time_slots[slot_index] = newSlot;
                this.setState({days:current_state},()=>{
                    console.log(this.state);
                });
            }
        });
    }

Demo

ravibagul91
  • 20,072
  • 5
  • 36
  • 59
-1

try this, here we are cloning previous state and updating the new one

slotClicked(day_index, slot_index) {
  let newStateDays = [...this.state.days]

  newStateDays[day_index].time_slots[slot_index].selected = true;
  this.setState({days: newStateDays});
}
Mosè Raguzzini
  • 15,399
  • 1
  • 31
  • 43
Harish
  • 1,841
  • 1
  • 13
  • 26
-1

To update a deeply nested data, you can use https://github.com/debitoor/dot-prop-immutable or something similar. In plain Javascript, it will be more verbose like below

const state = this.state;
const newState = {
    ...state,
    days: [
        ...state.days.slice(0, day_index),
        {
            ...state.days[day_index],
            time_slots: [
                ...state.days[day_index].time_slots.slice(0, slot_index),
                {...state.days[day_index].time_slots[slot_index], selected: true},
                ...state.days[day_index].time_slots.slice(slot_index + 1)
            ]
        },
        ...state.days.slice(day_index + 1)
    ]
}