React.js: loading JSON data with Fetch API and props from object array

94,026

Solution 1

Ok... Here's the working project. Got some help from @gumingfeng and @hkal.

Lessons learned:

  1. The React docs are ridiculously outdated.
  2. Straight JS vs. JSX is really no worse imo.
  3. Corrected some syntax errors in Fetch.
  4. Object array needs to be instantiated after data is loaded in order to pass DOM references in the constructors.
  5. However, calling setState() inside componentDidUpdate() triggers an infinite loop so had to set the object array directly and independently from state.
  6. When creating DOM elements from an array, React does not automatically assign the event handler to the particular element. In other words, it must be passed the array index so that can be used to access its values in the arrays.

Whew, I think that's it. Hope this helps others.

And I would just conclude by saying, give React a try without JSX. It's really not that bad :)

const { Component } = React;
const { render } = ReactDOM;

class CanvasAnimation extends Component {
    
    constructor(){
        super();
        this.state = {
            data: []
        };
    };
    
    componentDidMount() {
        fetch("data.json")
            .then( (response) => {
                return response.json() })   
                    .then( (json) => {
                        this.setState({data: json});
                    });
    };
    
    componentDidUpdate() {
        function animation(json) {
            return json.map( (data) => {
                return(
                    new CanvasSprite(
                        document.getElementById(data.id),
                        data.width,
                        data.height,
                        data.spriteSheetURL,
                        data.rows,
                        data.columns,
                        data.totalFrames)
                );
            });
        };
        //this.setState({animation: animation(this.state.data)}); //causes infinite loop
        this.animation = animation(this.state.data);
    };
    
    handleInteraction(event, index) {
        var offsetY = event.clientY -  document.getElementById(this.state.data[index].id).getBoundingClientRect().top;
        var relY = offsetY/this.state.data[index].height;
        this.animation[index].setFrame(relY);
    };
    
    render() {
        var canvases = this.state.data.map( (data, index) => {
            return (
                React.createElement('canvas', 
                                    {id : data.id,
                                    width : data.width,
                                    height : data.height,
                                    //style : {border: '5px solid white'},
                                    onMouseMove : (event) => this.handleInteraction(event, index)}
                                    )
            );
        });
        return(
            React.createElement('div', null, ...canvases)
        );
    };
    
};
  
    
render(
    React.createElement(CanvasAnimation, null),
    document.getElementById('content')
);

Solution 2

You have tons of syntax errors in your code, I have fixed them for you.

const { Component } = React;
const { render } = ReactDOM;

class CanvasAnimation extends Component {
  state = {
    data: []
  };

  loadData() {
    function animation(json) {
      return json.map(function(data) {
        return (
          new CanvasSprite(
            document.getElementById(data.id),
            data.width,
            data.height,
            data.spriteSheetURL,
            data.rows,
            data.columns,
            data.totalFrames
          )
        );
      });
    }
    fetch("data.json")
      .then(response => response.json())
      .then(json => {
        console.log(json);
        this.setState({
          data: json,
          animation: animation(json)
        });
      });
  }

  componentDidMount() {
    this.loadData();
  }

  handleInteraction(e) {
    var offsetY = e.clientY - e.node.getBoundingClientRect().top;
    var relY = offsetY/this.state.data.height;
    this.props.animation.setFrame(relY);
  }

  render() {
    var canvases = this.state.data.map(function(data) {
      return (
        <canvas
          id={data.id} 
          width={data.width} 
          height={data.height}
          style={{border: '5px white'}}
          onMouseOver={this.handleInteraction}
        />
      );
    });

    return (
      <div>{canvases}</div>
    );
  }
}

render(
  <CanvasAnimation />,
  content
);

I don't know the response of your API so I'm not sure if there's other to fix.

Some of the problems I have noticed:

  • Probably your indentation is wrong, because you had functions with double return statements. I suggest you to enable ESLint in your IDE to catch those errors.

  • You have not understood how setState works, you can't just do:

    this.setState({
      foo: 'bar',
      baa: myFn(this.state.foo)
    });
    

    Otherwise, this.state.foo inside myFn will refer to the old value of it, and not to the new one that you are setting right now.
    You'd have to do this.setState({foo: 'bar'}, () => this.setState({baa: myFn(this.state.foo)}), but then, it's better to do as I did in the code I have fixed above.

Share:
94,026

Related videos on Youtube

Sophia Gold
Author by

Sophia Gold

Simplicity over ease.

Updated on July 09, 2022

Comments

  • Sophia Gold
    Sophia Gold almost 2 years

    Totally new to react.js and after going through the tutorial and reading the docs, I'm still struggling a bit with using js fetch to load my data from a JSON file as well as setting properties from an array of objects. I'm also not certain I'm accessing DOM properties correctly in my event handler. I must be missing something rather straightforward.

    For reference, here's my code with the rest of the project and here's what it's supposed to look like.

    ETA: I had no idea from the docs that babel browser was deprecated so decided to just use straight Javascript with ES5 syntax instead of JSX. Code updated below, but it's still not rendering the markup.

    var CanvasAnimation = React.createClass({
        getInitialState: function() {
            return {data: []};
        },                            
        loadData: function() {
            /*
            fetch("data.json")
                .then(function(response) {
                    return response.json    
                        .then(function(json){
                            this.setState({data: json});
                        }.bind(this))
                }.bind(this));
            */
            const data = [
                { id: "stalkerOne", width: 225, height: 434, spriteSheetURL: 'spriteSheets/stalkerone.jpg', rows: 5, columns: 5, totalFrames: 24 },
                { id: "stalkerTwo", width: 175, height: 432, spriteSheetURL: 'spriteSheets/stalkertwo.jpg', rows: 6, columns: 5, totalFrames: 26 },
                { id: "stalkerThree", width: 251, height: 432, spriteSheetURL: 'spriteSheets/stalkerthree.jpg', rows: 6, columns: 5, totalFrames: 28 }
            ];
        },
        componentDidMount: function() {
            this.loadData();
        },
        componentDidUpdate: function() {
            function animation(json) {
                return json.map(function(data) {
                    return(
                        new CanvasSprite(
                            document.getElementById(data.id),
                            data.width,
                            data.height,
                            data.spriteSheetURL,
                            data.rows,
                            data.columns,
                            data.totalFrames)
                    );
                });
            };
            this.setState({animaton: animation(this.state.data)});  
        },
        handleInteraction: function(event, index) {
            var offsetY = event.clientY - event.node.getBoundingClientRect().top;
            var relY = offsetY/this.state.data.height;
            this.props.animation[index].setFrame(relY);
        },
        render: function() {
            var canvases = this.state.data.map(function(data, index) {
                return (
                    React.createElement('canvas', 
                                        id = data.id,
                                        width = data.width,
                                        height = data.height,
                                        style = 'border:5px solid white',
                                        onMouseOver= this.handleInteraction(event, index))
                );
            });
            return(
                React.createElement('div', canvases)
            );
        }
    });
      
        
    ReactDOM.render(
        React.createElement(CanvasAnimation, null),
        document.getElementById('content')
    ); 
    • azium
      azium almost 8 years
      No I mean you have the function animation() inside an object without a key that's not legal javascript code.. did you mean { data: json, canvas: animation() }. Upon closer inspection your animation function doesn't return anything
    • gu mingfeng
      gu mingfeng almost 8 years
      setState(function(previousState, currentProps) {...}),i think u mentioned code is like this.Even if this style,function setState is still change the this.state with the result of function as argument.
  • Sophia Gold
    Sophia Gold almost 8 years
    The problem was I used function animation(){ this.state.data.map() } instead of function animation(json){ json.map() }, which makes a lot more sense. But that should only effect props, not state. I put borders around the canvases so I could tell if they were at least rendering, regardless of the props being set, and they're not. It's not rendering.
  • Fez Vrasta
    Fez Vrasta almost 8 years
    You have A LOT of syntax errors, not just the problem with the animation function. Fix all of them with a linter, then your page will probably render.
  • Sophia Gold
    Sophia Gold almost 8 years
    I just pasted in your code. The code you wrote to answer this question is not rendering.
  • Fez Vrasta
    Fez Vrasta almost 8 years
    Sorry, SO is not a place where you ask for a code to be written and you get it. I gave you hints and answers to help you getting your code to work. That's all.
  • Sophia Gold
    Sophia Gold almost 8 years
    I didn't ask anyone to rewrite my code for me, but you did. You answered this question by rewriting all the code that wasn't working so if it's not rendering and throwing errors because of a lot of syntax errors then they're your syntax errors. This isn't an answer to a question...it's just different code that doesn't work. So I guess now I have the choice of continuing to debug mine or switching to debugging yours instead?
  • Sophia Gold
    Sophia Gold almost 8 years
    I would argue that SO is not a place where instead of answering specific questions verbally you write a new code sample that doesn't even run.
  • Fez Vrasta
    Fez Vrasta almost 8 years
    I have just fixed your code and used ES6 syntax to make it simpler to read, it's still your code. It doesn't render because my snippet has not access to your API
  • Sophia Gold
    Sophia Gold almost 8 years
    There's no API. There's a script with a JS class that does sprite based animation, a JSON file with the data, and three images. In the question I included a link to my code in the project file as well as another project running exactly as I'm trying to get this one except using a templating framework. I then included a third link in the first comment of your answer that shows your code replacing mine and still not running. I literally provided the entire project and then again an example of the project with your code. Both throw errors and do not even render the canvases.
  • Fez Vrasta
    Fez Vrasta almost 8 years
    Sorry, it's hard to debug compiled code like the link you have provided. Lot of the code of your script is missing so I have no idea how it should look. Here is a codepen with a working version of your script, but it misses a lot of parts that you have not provided codepen.io/FezVrasta/pen/OXJmRN?editors=0010
  • Sophia Gold
    Sophia Gold almost 8 years
    FYI...the issue here is that codepen runs on node.js and therefore uses babel-standalone instead of babel-core, which is apparently very different and explains why this jsx isn't compiling correctly on the client side.
  • Fez Vrasta
    Fez Vrasta almost 8 years
    I use the same syntax on my projects and it works flawlessly
  • Sophia Gold
    Sophia Gold almost 8 years
    It's not ES6 syntax, it's something else. What version of babel-core do you use on the client side? The example I posted where your code is throwing errors uses the latest available on cdnjs.
  • Sophia Gold
    Sophia Gold almost 8 years
    Also, when I add the animationEngine.js file from my example to your codepen I still can't access the one method from it I need: setFrame(). Someone commented I should be using this.state.animation instead of this.props, but that doesn’t seem right to me and isn’t making a difference. Babel issues aside, here’s a fork of your codepen that should be demonstrating the behavior linked to in my question, but isn’t: codepen.io/anon/pen/wWvqrV?editors=0011
  • Sophia Gold
    Sophia Gold almost 8 years
    FYI...this whole issue is because the docs rely on the deprecated babel browser and make no mention of webpack. Therefore, I only realized after several days wasted on this that you're using webpack to compile your jsx and that's why it doesn't work for me. Apologies for any friction this created. Although I do wish you had just looked at the example link in my question and told me that to begin with...
  • Fez Vrasta
    Fez Vrasta almost 8 years
    I'm sorry for this, but the default stack when someone develops with React.js, is webpack + babel, this is why I've not mentioned it.
  • Sophia Gold
    Sophia Gold almost 8 years
    Exactly. It's taken as granted...yet mentioned nowhere in the docs. In fact, the tutorial they have up contradicts it by using a deprecated version of babel browser. There's a whole issue thread on github about how the docs have been outdated for about a year now and fb is dragging their feet coming up with a replacement browser version rather than just putting together a tutorial on using webpack with react (which isn't obvious at all, but I found an example project).
  • Sophia Gold
    Sophia Gold almost 8 years
    Additionally, think I figured out the reason my CanvasSprite objects weren't instantiating is because setState() triggers it to render again so document.getElementByID() doesn't work. I need to set the state first in componentDidMount then the props later in componentShouldUpdate. I'll put this together as an answer once I can get webpack running and test it.
  • Giannis Dallas
    Giannis Dallas about 7 years
    This is the answer that helped me most. Calling fetch directly from componentDidMount() solves the undefined this frustration

Related