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

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 keys 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);
}

Related

Measuring a DOM node

I know that calling setState() immediately in componentDidMount() is a performance issue and it is better to use the constructor in some cases.
But in the last sentence of the React documentation it talks about a USE CASE that states that calling setState() immediately in componentDidMount() if it is VIABLE, what does THAT USE CASE mean.
Documentation React:
You may call setState() immediately in componentDidMount(). It will
trigger an extra rendering, but it will happen before the browser
updates the screen. This guarantees that even though the render() will
be called twice in this case, the user won’t see the intermediate
state. Use this pattern with caution because it often causes
performance issues. In most cases, you should be able to assign the
initial state in the constructor() instead. It can, however, be
necessary for cases like modals and tooltips when you need to measure
a DOM node before rendering something that depends on its size or
position.
I would like an simple example (with code), PLEASE, because I have not been able to see it in words alone
Sometimes you might need access to the DOM elements managed by React—for example, to focus a node, scroll to it, or measure its size and position. There is no built-in way to do those things in React, so you will need a ref to the DOM node. During the first render, the DOM nodes have not yet been created, so ref.current will be null. And during the rendering of updates, the DOM nodes haven’t been updated yet. So it’s too early to read them. This is why sometimes you have to wait to read ref and set state in componentDidMount, or in useEffect for functionnal components
Example to get the height of an element:
class DivSize extends Component {
constructor(props) {
super(props)
this.state = {
height: 0
}
}
componentDidMount() {
const height = this.divElement.clientHeight;
this.setState({ height });
}
render() {
return (
<div
ref={ (divElement) => { this.divElement = divElement } }
>
Size: <b>{this.state.height}px</b> but it should be 18px after the render
</div>
)
}
}

Best way to run a function when page refresh

I am trying to call a function when the page is refreshed. I adding a state if the page is rendered with the data I got from my backend end but I get an warning message "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state." Even though it works fine (except with the warning message), I dont think this is the best, most efficient way to do it?
If this is the best, most efficient way, how do I fix the waring message?
function Demo() {
constructor(){
this.state = {
username: "unknown",
rendered: false,
}
this.renderUserProfile = this.renderUserProfile.bind(this);
}
update(){
//code to retrieve data from backend node.js *
this.setState({ username: data });
this.setState({ rendered: true });
}
render(){
if (!this.state.rendered) {
this.update();
}
return (<p>demo</p>)
}
}
Thank you for your help!
Do never change state inside render, because every state (or prop) change will call render again. That is what the warning is telling you: you risk having infinite loops.
There is no need of a state param for "rendered", because your component will call render anyway as soon as this.setState({username: data}); executes. If you want something to happen then, add it in update just after the setState line.
Now let's imagine that you still really want it. If you don't want your component to render when the rendered state changes, then just don't use the React Component state, but any standard class attribute:
class MyComponent extends React.Component {
rendered = false
...
render() {
this.rendered = true
....
}
}
Just be aware that this looks super wrong (and useless) since it tries to go around what the React framework is good at.
Finally, from this code there is no way to know how you intend you have new data coming in. If it is an Ajax call, then you will call this.update with that data in the callback of your Ajax call - certainly not in render.

react-native flatlist detect when feed is loaded and rendered

I am implementing a react-native newsfeed with FlatList and I would like to detect then the feed is loaded, and the first few items rendered. The idea is that the splash page would be shown until the the newsfeed is rendered (https://facebook.github.io/react-native/blog/2018/01/18/implementing-twitters-app-loading-animation-in-react-native.html), at which point the splash feed would animate to the newsfeed. The question is where I do detect the isRendered event. My FlatList has two props that may be of interest:
class NewsFeed extends Component {
state = { data: [] }
loadData = async () => { ... }
renderItem = ({ item }) => { ... }
render () {
return <FlatList renderItem = {this.renderItem} data={this.state.data} />
}
}
I tried flipping the the isRendered boolean flag as a last line in loadData, but after transitioning from the landing screen, it still takes a fraction of a second (sometimes) before the pictures render. So I flipped the isRendered boolean flag in renderItem function, and we never advance past the splash screen at all, meaning the function is never run.
You might want to check the value of isRendered in the render function. If isRendered is still false, you display the splash screen. Then if the boolean is true, you can begin the animation from this component and display the list right away as the data is already loaded. To make this work, call setState to flip the isRendered boolean in loadData as you were doing before, so the render function gets called again once the data is loaded. (And you can call the loadData function in componentDidMount as this will be called right away when the component gets mounted).
If the animation is being called from a different component, you might want to load the data there and do the isRendered check in that component's render function, and then possibly pass the data as a prop to make sure the NewsFeed component is already loaded with the data.

componentDidMount or componentWillMount which one I need to use

I created a a box similar to twitter using react. I was looking at the react documentation found several component life cycles but not sure which one I should use to improve my code performance: componentDidMount or componentWillMount?
When I type something in my text box I see an update in the console printing the text box value. Can anyone help me understand which method to use and when in this case?
https://jsfiddle.net/c9zv7yf5/2/
class TwitterBox extends React.Component {
constructor(props) {
super(props);
this.state = { enteredTextBoxvalue : '' };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({enteredTextBoxvalue: event.target.value});
if((event.target.value).length > 3) {
this.setState({className : 'wholeContainer'});
//console.log("long characters");
}
}
render() {
return (<div>Hello {this.props.name}
<textarea className={this.state.className}
value={this.state.enteredTextBoxvalue}
onChange = {this.handleChange}>
there should be only 140 characters
</textarea>
</div>);
}
}
ReactDOM.render(
<TwitterBox name="World" />,
document.getElementById('container')
);
componentWillMount is called right before the component gets rendered.
componentDidMount is called right after the component gets rendered.
If you need to prepare data you use componentWillMount.
componentDidMount is popularly used among sending api calls or grabbing data just right after the component renders and is highly recommended to use that.
componentWillMount:
This function is called right before the component’s first render, so at first glance it appears to be a perfect place to put data fetching logic
componentDidMount:
Using componentDidMount makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.
As part of your question is about performance you could consider also having a look at shouldComponentUpdate to avoid reconciliation.
componentWillMount is invoked immediately before mounting occurs. It is called before render().
componentDidMount is invoked immediately after a component is mounted.
componentWillMount
component is about to render, plays the same role as constructor
there is no component in DOM yet you cannot do anything involving DOM
manipulation
calling setState() synchronously will not trigger
re-render as the component is not rendered yet
I would not recommend calling async /api requests here (technically there is no guaranty they will finish before component will be mounted, in this case the your component will not be re-rendered to apply those data)
componentDidMount
component has been rendered, it already seats in the DOM
you can perform manipulations involving DOM elements here (e.g. initialize third-party plugin)
call async /api requests, etc.

React - prevent lifecycle components from rendering the same data twice: WillReceiveProps & componentDidMount

Quick React question for Components specifically. I am using two lifecycle methods:
componentDidMount() -for retrieving data when component is first rendered
componentWillReceiveProps(nextProps) - for updating data when some parameters change
This works great. However, when I refresh the page with the component, both lifecycle methods are executed when one will suffice. The data on page is still correct, however it seems a bit inefficient. How can I combine these to lifecycles if possible?
Below example will call fetchTest() twice when page is refreshed. If I remove componentDidMount, then the data will not initially load if user refreshes the page.
Any ideas on how to have fetchTest() called once no matter how the user gets to the component?
componentDidMount() {
fetchTest(this.props.params.id);
// for initial component render
}
componentWillReceiveProps(nextProps) {
fetchTest(nextProps.params.id);
// for when (params.id) is changed. data within component is updated
}
You probably want to do
componentWillReceiveProps(nextProps) {
if (nextProps.params.id !== this.props.params.id) {
fetchTest(nextProps.params.id);
}
}

Categories

Resources