0

I have an array of 16 objects which I declare as a state in the constructor:

this.state = {
      todos:[...Array(16)].map((_, idx) => {
              return {active: false, idx}
            }), 
}

Their status will get updated through an ajax call in ComponentDidMount.

componentDidMount()
{
    var newTodos = this.state.todos;
    axios.get('my/url.html')
        .then(function(res)
        {
          newTodos.map((t)=>{
            if (something something)
            {
                t.active = true;
            }
            else
            {
                t.active = false; 
            }
          }
          this.setState({
            todos:newTodos,
          })
        }
}

and then finally, I render it:

render(){
   let todos = this.state.todos.map(t =>{
     if(t.active === true){
         console.log('true'}
     else{
         console.log('false')
     }
   }) 
   return (
     <div></div>
   )
}

They all appear as active = false in the console, they never go into the if condition. When I print out the entire state it appears not to be updated in the render method. In the console it says "value below was just updated now".

enter image description here

I thought changes to the state in ComponentWillMount will call the render function again?
How do I make that React will accept the new values of the state?

4
  • 2
    Can you align and fix your componentWillMount code? At a first look, it seems like the this.setState call is being called after the axios call, but the code you are sharing is neither aligned property, nor are all blocks closed so it's hard to say if the setState is being done from inside your then block or from inside the componentWillMount directly. If it's from the then you want to call it, you either have to use arrow functions or save the this context, if it's from the componentWillMount then you should await the axios call and make componentWillMount async Commented Jan 16, 2018 at 23:36
  • sorry. I edited it Commented Jan 16, 2018 at 23:41
  • 4
    then map returns a new array, it doesn't mutate in place. In the end your newTodos are still your old this.state.todos. And you still need to store the this context if you are not using arrow functions in the then callback (oh, and it's still not properly closed, the then function misses a ) , but the alignment did the trick) Commented Jan 16, 2018 at 23:43
  • 3
    Never heard of [...Array(16)].map, cool trick. For those who don't know, Array(16).map doesn't actually iterate over any of the items because they are empty slots stackoverflow.com/questions/27433075/… Commented Jan 17, 2018 at 0:39

2 Answers 2

1
componentDidMount()
{
    var newTodos = [];    // <<<<<<<<<<<<<
    axios.get('my/url.html')
        .then(function(res)
        {
          newTodos = this.state.todos.map((t)=>{   //<<<<<<<<<<<<<<<
            if (something something)
            {
                t.active = true;
            }
            else
            {
                t.active = false; 
            }
            return t; //<<<<<<<<<<<<<<<<<<
          }     // <<<<< are you missing a semi-colon?
          this.setState({
            todos:newTodos,
          })
        }
}

The map() argument (in your code) is a function, not an expression, so an explicit return must be provided. I.E.:

xxx.map( t => ( "return t is implicit" ) );   
xxx.map( t => { "return t must be explicit" } );  

And, as @DanielKhoroshko points out, your new variable points to this.state. And of course never, never, ever alter this.state directly. Since map() returns a new array, not the original as altered, that's why we use map() and not forEach()

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

Comments

1

That is because you are actually not providing any new state, but mutating it instead.

React uses shallow comparison be default (where to objects are equal if they reference the same memory address). And that's exactly what's happening here:

var newTodos = this.state.todos; // newTodos === this.state.todos

this.setState({  todos:newTodos }) // replaces two equal addresses, so it simply doesn't change anything

The easiest solution, though probably not the most performant would be to clone your todos array:

var newTodos = [...this.state.todos]; // newTodos !== this.state.todos

5 Comments

He is not mutating anything the map returns a new array but it doesn't get assigned
because elements of the array are objects, and some of these objects are being mutated (property active is changed)
and it's not only the map is not assinged to anything, the iterator of the map doesn't return anything either. but mutates the state
the author simply mistook it for forEach
True, I always find it confusing when people use a map instead of a forEach... I still don't think he will be able to do a lot with the response, but that might be just because his questions lacks data about what he wishes to do exactly (love those super secret to do apps ;))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.