React: componentDidMount wouldn't not rerendering the component

Created on 29 Oct 2016  Â·  23Comments  Â·  Source: facebook/react

Look , I got this problem, it shows on my console. which is
app.js:1 Uncaught TypeError: Cannot read property 'getHostNode' of null
and I don't know wtf is this Error, and another serious problem is here ,
here is my code :

componentDidMount() {
    console.log('mounted')
    getData(function (data) {   //get data from my backend
      console.log('got data: ' + data);
      this.setState({
        data: data
      })
    }.bind(this))
  }
  //data fetching function
  render() {
    console.log(this.state.data); // I loged this on my console. it should log twice right? 
    const log = this.state.data.filter(val => val.code == this.props.params.id)[0]; 
    console.log(log) // this will log twice as well 

and here is my log

[]
undefined

and that's it , I know this code may not look very good , but this problem is killing me , I try to debug it but I just can't

Unconfirmed

Most helpful comment

I did not understand this.canvas context so i did the aforementioned way.

My point was that you don't need to put canvas Fabric object into state. You should only use state for data used by the render function. While it's not harmful, it's better to put the Fabric object into a field such as this.canvas.

All 23 comments

It looks like getData never calls the callback. Also, please see Where To Get Support.

We won't be able to help you if you don't include a fiddle reproducing the issue, like the issue template you chose to delete asks you to.

Closing since there's been no follow-up, @JeremyWuuuuu if this is still an issue feel free to provide a fiddle reproducing the issue and we can look into re-opening.

I have a similar problem. The usecase of my app is, there is a list of images in the left side. When the user selects the image from those list, the selected image should be shown on canvas. In canvas the background image can be set using setBackgroundImage. I have created an object of canvas and called the function setCanvasBackground whose purpose is to set the canvas background with the selected image. And it is done inside componentDidMount. The default background image is shown on the canvas but the selected image is not shown on the canvas. I dont see componentDidUpdate re-rendering. That is why i used componentDidUpdate but using this canvas object will be re-created and if i select the image, the canvas will be set with the selected image but will show previous canvas too.

class ImageBoard extends React.Component {
  constructor() {
    super();
    this.state = {
      canvas: undefined,
      selected: undefined
    };
  }

  componentDidMount() {
    const canvas = new fabric.Canvas('canvas');
    this.setCanvasBackground(this.props.getSelectedImage.selectedImage, canvas);
  }

  componentDidUpdate() {
    const canvas = new fabric.Canvas('canvas');
    this.setCanvasBackground(this.props.getSelectedImage.selectedImage, canvas);
  }

  setCanvasBackground(image, canvas) {
    canvas.setBackgroundImage(image, function(e) {
    let iw = canvas.backgroundImage.width,
          ih = canvas.backgroundImage.height,
          cw = canvas.getWidth(),
          ch = canvas.getHeight();

    let iar = iw/ih,
      car = cw/ch;

    let nw = cw,
      nh = ch;

    if (iar > car){
      nh = ch;
      nw = nh * iar;
    }
    else if (iar < car){
      nw = cw;
      nh = cw/iar;
    }
    canvas.backgroundImage.width = nw;
    canvas.backgroundImage.height = nh;
    canvas.renderAll();
    });
  }

  render() {
    return(
      <div className="image-board">
         <canvas
           id="canvas"
           className="z-depth-1"
           width={600}
           height={400}
         />
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  console.log('state', state);
  return {
    getSelectedImage: state.getSelectedImage
  }
};

export default connect(mapStateToProps)(ImageBoard);

if i use componentDidUpdate, the result looks like following image

Image of canvas

if i use only componentDidMount, the canvas does not get updated with the selected image(does not happen re-rendering)
I am using reactjs v15.4.2

As noted in https://github.com/facebook/react/issues/8144#issuecomment-257099546, we can’t help you if you don’t provide a minimal reproducing example (that we can run, e.g. on JSBin) with a clear explanation of what you expected to happen, and what actually happens. Cheers!

I have produced an example in webpack bin. I did not want to create a new issue but i saw the similar problem and issue was already created so i posted my problem here. I am sorry if i violate the protocol.

I think when image is clicked, the componentDidMount should re-render the component because when action is fired the state of selectedimage changes and thus the new image should be shown in imageBoard in my case.

http://www.webpackbin.com/EyVxa6JLf

Thanks

@Tushant not sure if this is the problem, but this is a no-op.

  componentDidUpdate() {
    let oldCanvas = document.getElementById('canvas');
    oldCanvas = null;
  }

my componentDidMount is not rerendering the component even when the state of selected image is changed. So i tried componentDidUpdate to update the canvas. But to update the canvas first i need to clear the previous one. That is why i did so but it did not work either.

@Tushant

Can you reduce your example to just a React component? You don't seem to have a need for Redux, it's not really meant for simple state like this. Putting Redux there makes example way more complicated and harder to understand what you were trying to do.

@gaearon The example i showed you is only a part of my application i am developing to learn more on redux and react. There will be login system plus there will be lots of filtering system in the right side. That is why i am using redux.

Sure i will reduce the following example to react only. Sorry for late response due to timezone difference.

Thanks

Here is a reduced example. It is only on react now.

http://www.webpackbin.com/Ekmq-mbUM

Thanks for creating it. Unfortunately it seems like Webpackbin is broken for me. I can't edit files, nothing happens on keystrokes in any browser. 😞

Ultimately your problem doesn't appear related to React. On React side, this is all you need:

  componentDidMount() {
    this.setCanvasBackground(this.props.selectedImage);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.selectedImage !== this.props.selectedImage) {
      this.setCanvasBackground(this.props.selectedImage);
    }
  }

The only React-related problem I am seeing is that you rely on id to find the canvas. This is bad because your component won’t be reusable: you can’t have two galleries on the same page. To fix this, you would need to put a ref on your canvas and then do something with the DOM element:

  render() {
    return (
      <canvas ref={el => this.canvasEl = el} />
    );
  }

How you implement setCanvasBackground is up to you and depends on that library you're using for canvas. For example, you could do something like this:

  setCanvasBackground(image) {
    if (!this.canvas) {
      this.canvas = createCanvasSomehow(this.canvasEl); // depends on your library
    }
    this.canvas.setImageSomehow(image); // depends on your library
  }

If this doesn’t work, it’s a problem with how you’re using that library, and you’ll need to read its documentation to learn how to update image in an existing canvas.

Sorry i am a novice so could not understand if its react related issue or not. Thanks a lot for the knowledge. I got to learn new things as expected from you.

Thanks

No worries! I would recommend trying to use that API in vanilla JS first just to get a sense for how to use this library. Then use it with React once you’re sure about its API.

Sure i will do as you have said. I will practice in vanilla js to understand the concept of its api in js bin and come back again to use with react.

Thanks

One last question, i understand the this.canvasElbut what is this.canvas in the line if (!this.canvas)?

when i was reading the documentation of fabricjs, i found that

With native methods, we operate on context — an object representing entire canvas bitmap. In Fabric, we operate on objects — instantiate them, change their properties, and add them to canvas.

fabricjs does not work with the element so what i did is

const canvas = new fabric.Canvas(this.canvasEl);
and stored the object of canvas using this.setState in componentDidMountso i can use that object in componentDidUpdate. This way it worked for me. I did not understand this.canvas context so i did the aforementioned way.

Thanks a lot for your help.

@Tushant the if statement checks if the canvasEL exists, if not it creates it.

@robertistok Isn't it should be if (!this.canvasEl) ?

@Tushant I don't think so. If !this.canvasEl returns true, that means this.canvasEl is a falsy value and passing null or undefined to the function will not return something useful IMO.

I did not understand this.canvas context so i did the aforementioned way.

My point was that you don't need to put canvas Fabric object into state. You should only use state for data used by the render function. While it's not harmful, it's better to put the Fabric object into a field such as this.canvas.

Now i got it.
Thanks

Was this page helpful?
0 / 5 - 0 ratings