0

I want to dynamically generate output in my render() but I have run into a very strange situation. I have a process whereby I retrieve some data from a database using a fetch(). Once I get the data back, I determine the number of data records and then execute a for loop to populate an array with the returned data. I have a console.log() before the for loop to display the contents of my data receiving array and another console.log() as I populate the receiving array. For some reason, as I populate a specific occurrence of the array, all occurrences of the array appear to be changing. This is the entire code that I have:

import React from 'react';
import '../styles/app.css';


class testpage extends React.Component {


constructor(props) {
    super(props);
    this.state = {
        productArray: []
    };

}


componentWillMount() {

    var tempData = '';
    var numberOfRecords = 0;

    let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
    const options = { method: 'GET' };

    fetch(url, options)
    .then(function(response) {
        return response.json();
    })
    .then(function(myJson) {
        if (myJson == undefined) 
        {
            console.log("fetch failed");
        } 
        else 
        {
            //inspect the data that the WebAPI returned 
            var return_code = myJson[0].return_code;
            if (return_code == "Default Return code"){
                tempData = '';
                numberOfRecords = -2;
            } else {
                tempData = JSON.parse(myJson[0].return_string)
                numberOfRecords = tempData.barcode.length;

                var counter = 0;
                var productArray = new Array;
                var product = {
                    barcode: '',
                    name: '',
                    description: '',
                    image: '',
                    price: ''
                }


                for (counter=0;counter<numberOfRecords;counter++) {
                    product.barcode = tempData.barcode[counter];
                    product.name = tempData.name[counter];
                    productArray[counter] = product;
                }                  
            }
        }
    }); 
  }

render() {
    <div>
        <div>
            {this.state.productArray[0].barcode}
        </div>
    </div>
}

}

export default testpage;

Here is an image of what I see in the console.log() when the loop counter = 0: enter image description here

Notice the barcode value of 5000159459228? This value gets pushed into productArray[0].barcode. And this is what I expected.

Here is an image of what I see in the console.log() when the loop counter = 1: enter image description here Here, the barcode of the record read is 5000159459230. This value should go into productArray1.barcode, which the image shows it does. However, the image also shows that the value of productArray[0].barcode has changed from 5000159459228 (the value of the first record) to 5000159459230 (the value of the second record).

Here is an image from the 3rd time through the loop: enter image description here

Again, a new record with barcode = 5000159459231. It appears that this value gets pushed into productArray2.barcode but productArray[0].barcode and productArray1.barcode have now been changed.

How is that possible?

Eventually, the goal is to dynamically render the data that is retrieved.

I thank you in advance for any assistance.

Jonathan Small
  • 1,027
  • 3
  • 18
  • 40
  • In addition to the given answer here, also check this: https://stackoverflow.com/questions/11284663/console-log-shows-the-changed-value-of-a-variable-before-the-value-actually-ch and all other follow-ups in that question :) – devserkan Aug 25 '18 at 21:25
  • I changed my debugging output as you suggested. The results show that the array is empty entering my for loop. However, the output that is generated within my for loop shows that as I add data into the productArray occurrence, it is changing all previously populated occurrences with the same data. I have edited my original post to include the changed code and the output. – Jonathan Small Aug 26 '18 at 11:43
  • Too much log but what you described here is normal and explained in the other answers. If `console.log` is the only problem do not bother. If the problem is reflecting your data properly to the DOM then change your render logic. Personally, I don't like for loops and changing objects directly with mutating their properties. – devserkan Aug 26 '18 at 12:39
  • @devserkan I changed my render logic to
    {this.state.productArray[0].barcode}
    and I changed the definition of my state variable from productArray: '' to productArray: [] and I changed the renderELS to a componentWillMount(). Now I get an error that states Uncaught (in promise) TypeError: Cannot read property '_currentElement' of null at . There must be some mistake in the way I am either defining my state variable or the way I am trying to reference it in the render. I think.
    – Jonathan Small Aug 26 '18 at 13:38
  • Your API does not return a healthy response I think? It is not JSON and not a proper XML I suppose. – devserkan Aug 26 '18 at 15:59
  • @devserkan I dont think its an issue regarding the data I am getting back. I thinks its more an issue of how I am trying to store the data into a state variable and then render that information. I removed the state variable productArray and added productArray_barcode and productArray_name. I populate these new fields in the fetch. In my render, I am able to reference the fields as this.state.productArray_barcode[0] and this.state.productArray_name[0]. This is a move forward. – Jonathan Small Aug 26 '18 at 16:08
  • @devserkan BUT I really need to do is have something like this.state.productArray[0].barcode and this.state.productArray[0].name. This way, I can pass this.state.productArray in an arrow function with map so I can render the output dynamically as opposed to hard code the array instance – Jonathan Small Aug 26 '18 at 16:08
  • This is a simple process, believe me. After getting a response you can set it to your state then dynamically render items with a map. But, your API does not return anything healthy. Your code does not work on my side. You don't have a return statement in your render. I fixed it, then I tried to log the first response. Nothing. – devserkan Aug 26 '18 at 16:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178806/discussion-between-jonathan-small-and-devserkan). – Jonathan Small Aug 26 '18 at 16:27

3 Answers3

3

Console output can generally be relied on for primitives but not for objects. Console implementation is specific to the environment where a script is executed (browser).

It is object reference that is being outputted, not object snapshot. When an object is changed, its representation in console can change as well.

Notice that in Chrome console the array was initially logged as [] empty array, while next log entry is [...] non-empty array.

To output object snapshot that doesn't change retroactively, use console.log(JSON.stringify(array)) or other serialized primitive value.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I changed my debugging output as you suggested. The results show that the array is empty entering my for loop. However, the output that is generated within my for loop shows that as I add data into the productArray occurrence, it is changing all previously populated occurrences with the same data. I have edited my original post to include the changed code and the output. – Jonathan Small Aug 26 '18 at 11:43
0

I would recommend to use chrome devtools for the debugging and add break point where array is populating.
Also you can add debugger in any where in code It will prompt devtools then. I second @estus about changes reflect upon object updation where ever you have used Console.

Sakhi Mansoor
  • 7,832
  • 5
  • 22
  • 37
0

After the chat and learned how the data shape is, with the help of Code Review, this is how we can do this:

const json = [{
  return_string: {
    "barcode": ["5000159459228", "5000159459229", "5000159459230"],
    "scan_date": ["20180825173416", "20180825173416", "20180825173416"],
    "name": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz  x  6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz  x  6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz  x  6)"],
    "description": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz  x  6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz  x  6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz  x  6)"],
    "image": ["http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg"],
    "price": ["1", "2", "3"]
  }
}];

const fakeRequest = () => new Promise( resolve =>
  setTimeout( () => resolve( json ) )
);

class App extends React.Component {
  state = {
    data: "",
  }

  componentDidMount() {
    fakeRequest()
      .then( res => {
        this.setState({
          data: res[0].return_string,
        })
      })
  }

  renderData = () => {
    const { data } = this.state;
    const values = Object.values(data);
    const keys = Object.keys(data);
    
    const transposed = values[0].map((col, i) =>
      values.map(row => row[i]));
    
    const items = transposed.map(itemArr =>
      keys.reduce((acc, key, i) => (
        { ...acc, [key]: itemArr[i] }), {})
    );
    
    return items.map( item => (
      <div style={{ border: "1px solid black", marginBottom: "3px" }}>
        <p>Barcode: {item.barcode}</p>
        <p>Scan date: {item.scan_date}</p>
        <p>Name: {item.name}</p>
        <p>Description: {item.description}</p>
        <p>Image: {item.image}</p>
        <p>Price: {item.price}</p>
      </div>
    ) )
  }

  render() {
    return <div>{this.state.data && this.renderData()}</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>

The hard part is converting the data into an array of objects properly. Since, this shape of data is a little bit strange to me :) An object containing properties of arrays. And those array elements are matched by order. Phew :)

devserkan
  • 16,870
  • 4
  • 31
  • 47