2

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)
    })
}, []) ```



1
  • Technical detail, but a very important one: that's not JSON. By definition, JSON is string data. Once it's been parsed it's normal JS data like any other code you would have written yourself. Commented Mar 23, 2020 at 22:58

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the tip about passing a function to setPersons() to get the prev value.
1

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]);
        });
}, []);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.