1

I have an array as such:

product = [
    {
        name: " size one",
        price: 1
      },
      {
        name: "size two",
        price: 2
      },
      ,
      {
        name: "size three",
        price: 3
      }
    ];

I had previously separated odd and even array indexes like this:

for (let i=0; i< product.length; i++ ) {
      if (i % 2 === 0 ){
        evenArray.push(product[i])
      } else {
        oddArray.push(product[i])
      }
    };

But I would like to move this to the constructor and out of render. I have it written like this in a componenetDidMount():

  for (let i=0; i< this.state.product.length; i++ ) {
      if (i % 2 === 0 ){
        this.setState({
        evenArray: [...this.state.evenArray, this.state.product[i]]
      });
      } else {
        this.setState({
        oddArray: [...this.state.oddArray, this.state.product[i]]
      });
      }
    };

But it doesn't work. I'm familiar with the idea that you cannot change the state directly but update it only which is why .push() can't be used but how come the above does not work?

-----EDIT OF REAL CODE-----

componentDidMount() {
  const oauth = OAuth({
    consumer: {
      key: 'XXXXXXXXX',
      secret: 'XXXXXXXXXXX',
    },
    signature_method: 'HMAC-SHA1',
    hash_function(base_string, key) {
      return crypto.createHmac('sha1', key).update(base_string).digest('base64');
    }
  });

  const request_data1 = {
    url: 'http://localhost:8888/wp-json/wc/v2/products/28/variations?per_page=15',
    method: 'GET',
  };

  const request_data2 = {
    url: 'http://localhost:8888/wp-json/wc/v2/products/28/',
    method: 'GET',
  };

  fetch(request_data1.url, {
    method: request_data1.method,
    headers: oauth.toHeader(oauth.authorize(request_data1))
  })
    .then(response => response.json())
    .then(response => {
      this.setState({
         products1: response.reverse()
      })
   })

   fetch(request_data2.url, {
     method: request_data2.method,
     headers: oauth.toHeader(oauth.authorize(request_data2))
   })
     .then(response => response.json())
     .then(response => {
       this.setState({
          products2: response
       })
    })

    const evenArray = [];
    const oddArray = [];
    for (let i = 0; i < this.state.products1.length; i++) {
      if (i % 2 === 0) {
        evenArray.push( this.state.products1[i] );
      } else {
        oddArray.push(this.state.products1[i]);
      }
    };

    this.setState( prevState => ({
        evenArray: [ ...prevState.evenArray, ...evenArray ],
        oddArray: [...prevState.oddArray, ...oddArray],
    }));

}
kadddeee
  • 498
  • 8
  • 20
  • What error do you get? – Shahaf Antwarg Aug 14 '18 at 21:41
  • There not necessary and error. Its just a console.log of both oddArray and evenArray return empty. – kadddeee Aug 14 '18 at 21:42
  • the direct fix is covered by the answers below, but since it's noteworthy to understand the issue: part of the problem is that setState doesn't necessarily happen immediately. So you're updating the state with a modification of the old state _potentially before the previous update to old state has happened_ For a lot of these situations you can use _functional `setState()`_ see https://stackoverflow.com/questions/48209452/when-to-use-functional-setstate – pentaphobe Aug 14 '18 at 22:08

2 Answers2

2

You can't set your state successfully since your for loop finishes before you set the state. I don't like for loops for this reason and we generally don't use them but you can do it in this way:

class App extends React.Component {
  state = {
    product: [
      {
        name: " size one",
        price: 1
      },
      {
        name: "size two",
        price: 2
      },
      {
        name: "size three",
        price: 3,
      }
    ],
    evenArray: [],
    oddArray: [],
  }

  componentDidMount() {
    const evenArray = [];
    const oddArray = [];
    for (let i = 0; i < this.state.product.length; i++) {
      if (i % 2 === 0) {
        evenArray.push( this.state.product[i] );
      } else {
        oddArray.push(this.state.product[i]);
      }
    };
    
    this.setState( prevState => ({
        evenArray: [ ...prevState.evenArray, ...evenArray ],
        oddArray: [...prevState.oddArray, ...oddArray],
    }));
  }

  render() {
    console.log( this.state );
    return <div>
        Evens: {JSON.stringify(this.state.evenArray)}
        Odds: {JSON.stringify(this.state.oddArray)}
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Update after comments

Since you are fetching your data, before it finishes again you are trying to set your state. I tried to mimic the situation. This is just one way of many solutions.

const product1 = [
  {
    name: " size one",
    price: 1
  },
  {
    name: "size two",
    price: 2
  },
  {
    name: "size three",
    price: 3,
  }
];

const fakeRequest = () => new Promise( resolve =>
  setTimeout( () => resolve( product1 ), 2000 )
);

class App extends React.Component {
  state = {
    product1: [],
    evenArray: [],
    oddArray: [],
  }

  componentDidMount() {
    fakeRequest()
      .then( product1 => {
        const evenArray = product1.filter((el, i) => i % 2 === 0);
        const oddArray = product1.filter((el, i) => i % 2 !== 0);
        this.setState( prevState => ( {
          product1: [ ...prevState.product1, ...product1 ],
          evenArray: [...prevState.evenArray, ...evenArray ],
          oddArray: [...prevState.oddArray, ...oddArray],
        } ))
      })
  }

  render() {
    console.log(this.state);
    return <div>
      { !this.state.product1.length && <p>Waiting for 2 seconds...</p> }
      Evens: {JSON.stringify(this.state.evenArray)}
      Odds: {JSON.stringify(this.state.oddArray)}
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Second update

I used the fakeRequest to mimic your situation, don't use it. Your code will probably something like this:

fetch(request_data1.url, {
    method: request_data1.method,
    headers: oauth.toHeader(oauth.authorize(request_data1))
  })
    .then(response => response.json())
    .then(response => {
         const products1 = response.reverse();
         const evenArray = products1.filter((el, i) => i % 2 === 0);
         const oddArray = products1.filter((el, i) => i % 2 !== 0);
         this.setState( prevState => ( {
              product1: [ ...prevState.products1, ...product1 ],
              evenArray: [...prevState.evenArray, ...evenArray ],
              oddArray: [...prevState.oddArray, ...oddArray],
         } ))
})
devserkan
  • 16,870
  • 4
  • 31
  • 47
  • Hey @devserkan. I followed both of your approaches but some how its not working for me. In my real code I am pulling data from woocommerce. So my `componentDidMount` has fetch functions in it. Could this potentially be causing an issue? – kadddeee Aug 14 '18 at 22:30
  • Can you share that part also, please? Just update your question. – devserkan Aug 14 '18 at 22:32
  • Thank you derserkan. for `const fakeRequest = () => new Promise( resolve => setTimeout( () => resolve( product1 ), 2000 ) );` I get an error that product1 is not defined. I put outside of the component like you have, is this wrong? – kadddeee Aug 14 '18 at 23:28
  • I've made a second update. You don't need the `fakeRequest` since I've made it up to mimic the fetch request. – devserkan Aug 14 '18 at 23:34
  • 1
    !!! I could jump through the skin to hug you! Awesome its working perfectly. Embarrassing that I just copied the fakeRequest() not realising its for example haha, must be tired. – kadddeee Aug 14 '18 at 23:49
  • No need to be embarrassed, we all get tired time to time :) You are welcome. – devserkan Aug 14 '18 at 23:51
0

You shouldn't call this.setState in the constructor. Instead just set it as the initial state:

constructor(props) {
  super(props);
  this.state = {
    evenArray: evenArray, 
    oddArray: oddArray,
  };
}
Danny Delott
  • 6,756
  • 3
  • 33
  • 57