Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug with useState / useEffect react spread operator

I am having an issue where I am setting state with a spread operator but the function call in useEffect only ever sets the last value pulled from the API into state.

// get all events to display on load
const getEvents = () => {
    console.log(props.id);
    axios.get(API.EVENTS.ROOT + props.id).then(
        res => {
            const data = res.data;
            setEvent(data);
            console.log(data);
            // event start and end dates
            setStart(new Date(data.start));
            setEnd(new Date(data.finish));
            // breakout dates
            for (let i = 0; i < data.breakouts.length; i++) {
                console.log(data.breakouts[i].start);
                data.breakouts[i].start = new Date(data.breakouts[i].start);
                data.breakouts[i].end = new Date(data.breakouts[i].end);
                console.log(data.breakouts[i].name, "test" + data.breakouts[i].start);
                // set date picker initial value 
                setFields({
                    ...fields,
                    [data.breakouts[i].name]: data.breakouts[i].start
                })
                console.log(fields);
            }

            setBreakoutRow(data.breakouts);
            console.log(breakoutRow);

        }
    ).catch(err => {
        console.log(err);
    })
}
// get events on load
useEffect(() => {
    console.log(props);
    getEvents();
}, []);

I add the fields state variable or the getEvents function as a dependency it does update the state of fields for all values but it results in an infinite loop of death. This is giving me blank dates in mapped rows for all but the last.

Another item of note is that after the page loads the fields object can be updated no problem through a function call, as so:

const handleDateChange = (dateName, dateValue) => {
        console.log(dateName, dateValue);
        setFields({
            ...fields,
            [dateName]: dateValue
        })
        console.log(fields);
    }

This leads me to believe there is something wrong with my useEffect hook but I am not sure where it's going wrong.

like image 727
Husk Rekoms Avatar asked Dec 06 '25 08:12

Husk Rekoms


1 Answers

Reason is how you're accessing previous value to add new element:

for (let i = 0; i < data.breakouts.length; i++) {
  data.breakouts[i].start = new Date(data.breakouts[i].start);
  setFields({
    ...fields,
    [data.breakouts[i].name]: data.breakouts[i].start
  })
}

since fields will be updated only on next render, you are adding single element(but each loop it's different element) to the array.

The same would be without hooks/react:

const start = [];
for(let i = 0; i< 5; i++) console.log([...start, i]);

This will never output [0,1,2,3,4].

What can you do.

Option 1. Collect data in interm variable:

const temp = {};
for (let i = 0; i < data.breakouts.length; i++) {
  data.breakouts[i].start = new Date(data.breakouts[i].start);
  data.breakouts[i].end = new Date(data.breakouts[i].end);
  temp[data.breakouts[i].name] = data.breakouts[i].start
}
setFields({
  ...fields,
  ...temp
});

Option 2. Use functional version of setter as an accumulator:

for (let i = 0; i < data.breakouts.length; i++) {
  data.breakouts[i].start = new Date(data.breakouts[i].start);
  setFields(({fields: prevFields}) => ({
    ...prevFields,
    [data.breakouts[i].name]: data.breakouts[i].start
  }))
}

I'd prefer first option for 2 reasons:

  1. it would be better finally move data transform closer to call point, so instead of axios.get there would be some API.getMeaningfullThings returning structure you need; so it's easier to refactor
  2. maybe slighter more code shows better what's going on(it's just a transforming, not a filtering or any React-specific thing)
like image 196
skyboyer Avatar answered Dec 09 '25 00:12

skyboyer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!