Rin
Rin

Reputation: 23

Can't concatenate json server data into array using useEffect hook in React

I'm new to React and fullstack dev in general so bear with me. I'm trying to concatenate data from a json-server (which I've received using Axios) to an array. I'm using React hooks to update state. For some reason, even when personObject shows up fine in the console (there are 4 elements each with a name and an ID), an empty array pops up when I console.log the persons array. Here's the useEffect code.

const [ persons, setPersons] = useState([])

useEffect(() => {

console.log('effect')
  axios
    .get('http://localhost:3001/persons')
    .then(response => {
      console.log('promise fulfilled')
      const temp = response.data

      temp.forEach(function(e) {
        let personObject = {
          content: e.name,
          id: e.id,
        }
        console.log(personObject)
        setPersons(persons.concat(personObject))

      })

      console.log(persons)
    })
}, []) ```



Upvotes: 2

Views: 1360

Answers (2)

JMadelaine
JMadelaine

Reputation: 2964

Updating state is asynchronous. For example:

const state  = {
  myValue: 1
}

const someFunctionThatUpdatesState = () => {
    console.log("before", state.myValue)
    setState({ myValue: 2})
    console.log("after", state.myValue)
}

What do you think the console shows?

You might expect it to log before, 1 and after, 2, but what actually gets logged is before, 1, after, 1.

When you set state, you are not actually setting state, you are scheduling a state update. You're basically telling React that state needs to be updated to the value you pass to setState.

React doesn't actually update state until the component re-renders, so in your case, persons will only have the new value once the function ends and the component updates.

Speaking of which, your function has a flaw:

 setPersons(persons.concat(personObject))

Since persons does not change until re-render, you are basically cancelling out your setState call on every iteration. You're essentially doing this:

setPersons([].concat(personObject1))
setPersons([].concat(personObject2))
setPersons([].concat(personObject3))
setPersons([].concat(personObject4))

Your result will end up as an array with only the final value in it, all the rest are overwritten.

What you want is something like this:

 .then(response => {
  console.log('promise fulfilled')

  // make sure data actually exists
  if (response && response.data) {
      // use prev, which is the up-to-date state value
      setPersons(prev => [
          // spread the current value of `prev`
          ...prev,
          // map the data and spread the resulting array
          ...response.data.map(item => ({
              id: item.id,
              content: item.name,
          })),
      ])
  }
})

The changes I've made are:

  • Check if the data exists! What happens if data is undefined?
  • Use map instead of forEach. What you're doing is mapping your data array to an array in state, so just use the map function instead of forEach.
  • Replace concat with spread syntax .... It's more succinct and easier to read.
  • Only call setPersons once, and pass a function to setPersons.

If your new state value depends on old state (like with persons, you are appending the new data to the array of old data), you should pass a function to setState. prev is the most up-to-date state value, so you can depend on it. Doing something like setPersons(persons....) (using persons inside setPersons is prone to errors.

Upvotes: 1

Rocky Sims
Rocky Sims

Reputation: 3598

It sounds like you're trying to fetch data, use that data to create a newPersons array, and add the newPersons to the end of the existing persons array. See if this does the trick for you:

const [persons, setPersons] = useState([])

useEffect(() => {
    console.log('effect');
    axios
        .get('http://localhost:3001/persons')
        .then(response => {
            console.log('promise fulfilled');
            const newPersons = response.data.map(person => ({
                content: person.name,
                id: person.id
            }));
            setPersons(prev => [...prev, ...newPersons]);
        });
}, []);

Upvotes: 1

Related Questions