简体   繁体   中英

How to reset state on props change in an already mounted component?

I have a <BlogPost> component which could've been a Stateless Function Component, but turned out as a Class Stateful Component because of the following:

The blogPost items that it renders (receiving as props ) have images embedded in their html marked content which I parse using the marked library and render as a blog post with images in between its paragraphs, h1, h2, h3, etc.

The fact is that I need to preload those images before rendering the post content to my client. I think it's a UX disaster if you start reading a paragraph and all of a sudden it moves down 400px because the image that was being loaded has been mounted to the DOM during the time you were reading it.

So I prefer to hold on by rendering a <Spinner/> until my images are ready. That's why the <BlogPost> is a class component with the following code:

class BlogPost extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      pending: true,
      imagesToLoad: 0,
      imagesLoaded: 0
    };
  }

  preloadImages(blogPostMedia) {
    this.setState({
      pending: true,
      imagesToLoad: 0,
      imagesLoaded: 0
    });

    ... some more code ...
    // Get images urls and create <img> elements to force browser download
    // Set pending to false, and imagesToLoad will be = imagedLoaded
  }

  UNSAFE_componentWillReceiveProps(nextProps) {
    if (this.props !== nextProps) {
      this.preloadImages(nextProps.singleBlogPost.media);
    }
  }

  componentDidMount() {
    this.preloadImages(this.props.singleBlogPost.media);
  }

  render() {
    return(
        this.state.pending ?
          <Spinner/>
        : (this.state.imagesLoaded < this.state.imagesToLoad) ?
            <Spinner/>
          : <BlogPostStyledDiv dangerouslySetInnerHTML={getParsedMarkdown(this.props.singleBlogPost.content)}/>
    );
  }
}

export default BlogPost;

At first I was calling the preloadImages() only inside the componentDidMount() method. And that works flawlessly for the first post I render with it.

But as soon as I would click on the next post link; since my <BlogPost> component is already mounted, componentDidMount() doesn't get called again and all the subsequent posts I would render by clicking on links (this is a Single Page App) wouldn't benefit from the preloadImages() feature.

So I needed a way to reset the state and preload the images of the new blogPost received as props inside an update cycle, since the <BlogPost> component it's already mounted.

I decided to call the same preloadImages() function from inside the UNSAFE_componentWillReceiveProps() method. Basically it is reseting my state to initial conditions, so a <Spinner/> shows up right away, and the blog post only renders when all the images have been loaded.

It's working as intended, but since the name of the method contains the word "UNSAFE", I'm curious if there's a better way to do it. Even though I think I'm not doing anything "unsafe" inside of it. My component is still respectful to its props and doesn't change them in anyway. It just been reset to its initial behavior.

RECAP: What I need is a way to reset my already mounted component to its initial state and call the preloadImages() method (inside an update cycle) so it will behave as it was freshly mounted. Is there a better way or what I did is just fine? Thanks.

I would stop using componentWillReceiveProps() ( resource ). If you don't want the jarring effect, one way you can avoid it is to load the information from <BlogPost/> 's parent, and only once the information is loaded, to pass it into <BlogPost/> as a prop.

But anyway, you can use key s to reset a component back to its original state by recreating it from scratch ( resource ).

componentWillReceiveProps is deprecated, it's supposed to be replaced with either getDerivedStateFromProps or componentDidUpdate , depending on the case.

Since preloadImages is asynchronous side effect, it should be called in both componentDidMount and componentDidUpdate :

  componentDidMount() {
    this.preloadImages(this.props.singleBlogPost.media);
  }

  componentDidUpdate() {
    this.preloadImages(this.props.singleBlogPost.media);
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM