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.
Related
In my code, app is the highest (functional) component. It renders piggybank (which is a class component) which renders piggychild (which is a functional component).
Each component 'console.logs' the name of itself (in the render method for class-component; simply in the function for a functional component) and has a 'mount' function (for the class-component, this means a componentDidMount method; for functional component, this means a useEffect hook whose callback's 2nd parameter is an empty array) which logs 'mount' to the console.
As you can see in the picture, PiggyBank's mount fires first, then PiggyChild's, then App's.
Is there a rule in React that governs the order in which components' 'mount' functions occur?
Or is it enough to just know that mount functions occur after all components are rendered?
useEffect has a different timing than componentDidMount. The hook that's closest to CDM is actually useLayoutEffect.
Here's a bit from ReactTraining's blog on useEffect:
They run at different times
First, let's talk about the timing of each. componentDidMount runs
after the component mounts. As the docs say, if you set state
immediately (synchronously) then React knows how to trigger an extra
render and use the second render's response as the initial UI so the
user doesn't see a flicker. Imagine you need to read the width of a
DOM element with componentDidMount and want to update state to reflect
something about the width. Imagine this sequence of events:
Component renders for the first time.
The return value of render() is used to mount new DOM.
componentDidMount fires and sets state immediately (not in an async
callback)
The state change means render() is called again and returns new JSX
which replaces the previous render.
The browser only shows the second render to avoid flicker.
It's nice that this is how it works for when we need it. But most the
time we don't need this pre-optimized approach because we're doing
asynchronous network calls and then setting state after the paint to
the screen.
componentDidMount and useEffect run after the mount. However useEffect
runs after the paint has been committed to the screen as opposed to
before. This means you would get a flicker if you needed to read from
the DOM, then synchronously set state to make new UI.
How do get the old behavior back when we need it?
useLayoutEffect was designed to have the same timing as
componentDidMount. So useLayoutEffect(fn, []) is a much closer match
to componentDidMount() than useEffect(fn, []) -- at least from a
timing standpoint.
Does that mean we should be using useLayoutEffect instead?
Probably not.
If you do want to avoid that flicker by synchronously setting state,
then use useLayoutEffect. But since those are rare cases, you'll want
to use useEffect most of the time.
I've added an example which has both Layout Effects and normal Effects in the scenario you described:
The results of running this are as follows. The main thing to note with this is that The Layout effects happen in a sensible order with the componentDidMount lifecycle hook, whereas the effects happen later.
App
PiggyBank
PiggyChild
PiggyChild LayoutEffect
PiggyBank Mount
App LayoutEffect
PiggyChild Effect
App Effect
const {useEffect, useLayoutEffect} = React;
function App(){
console.log('App');
useLayoutEffect(()=>console.log('App LayoutEffect'),[]);
useEffect(()=>console.log('App Effect'),[]);
return <PiggyBank/>;
}
class PiggyBank extends React.Component{
componentDidMount(){
console.log('PiggyBank Mount');
}
render(){
console.log('PiggyBank');
return <PiggyChild/>
}
}
function PiggyChild(){
console.log('PiggyChild');
useLayoutEffect(()=>console.log('PiggyChild LayoutEffect'),[]);
useEffect(()=>console.log('PiggyChild Effect'),[]);
return <div/>;
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Without the LayoutEffects, the order will be:
The console.logs in the render functions, in order from parent to child, since part of rendering each component is rendering the children
Then the componentDidMount runs immediately after it finishes mounting. If there are multiple components with componentDidMounts or useLayoutEffects, these would be called in order from from child to parent as they finish mounting, but before the paint is committed (as mentioned above).
The useEffects run in order from child to parent as they finished mounting in that order, but they will always run after the componentDidMounts and useLayoutEffects due to the timing mentioned above.
const {useEffect} = React;
function App(){
console.log('App');
// useLayoutEffect(()=>console.log('App LayoutEffect'),[]);
useEffect(()=>console.log('App Effect'),[]);
return <PiggyBank/>;
}
class PiggyBank extends React.Component{
componentDidMount(){
console.log('PiggyBank Mount');
}
render(){
console.log('PiggyBank');
return <PiggyChild/>
}
}
function PiggyChild(){
console.log('PiggyChild');
// useLayoutEffect(()=>console.log('PiggyChild LayoutEffect'),[]);
useEffect(()=>console.log('PiggyChild Effect'),[]);
return <div/>;
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Actually it makes absolute sense, and order of useEffect in hierarchy is the same as in componentDidMount.
The thing is that componentDidMount fires when all children mounted (so you would see first child event, and only then parent one) - so same order as you see.
For more details about order of componentDidMount you could see here - Order of componentDidMount in React components hierarchy
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);
}
My TranslationDetail component is passed an id upon opening, and based on this an external api call is triggered in the class constructor, receiving data to the state, and this data being displayed on TranslationDetail.
//Routing:
<Route path="/translation/:id" component={TranslationDetail}/>
//Class:
class TranslationDetail extends Component {
constructor(props){
super(props);
this.props.fetchTrans(this.props.params.id);
}
This all works fine if I enter the url manually. In case I'd like to use react-router e.g. for displaying the next item like below the url does change, but the api call is not triggered, and the data will remain the same.
<button
type="button"
onClick={() =>
browserHistory.push(`/translation/${Number(this.props.params.id)+1}`)}>
Next
</button>
Please bear in mind that I'm a total beginner. The reason why this is happening is I believe that the constructor is run only once, thus no further api call is triggered.
How can I solve this?
Do I need to listed to props and call a function on change? If yes, how?
Constructor is not a right place to make API calls.
You need to use lifecycle events:
componentDidMount to run the initial fetch.
componentDidUpdate to make the subsequent calls.
Make sure to compare the props with the previous props in componentDidUpdate to avoid fetching if the specific prop you care about hasn't changed.
class TranslationDetail extends Component {
componentDidMount() {
this.fetchTrans();
}
componentDidUpdate(prevProps) {
if (prevProps.params.id !== this.props.params.id) {
this.fetchTrans();
}
}
fetchTrans() {
this.props.fetchTrans(this.props.params.id);
}
}
From React 16.3 and onwards componentWillMount, componentWillUpdate and componentWillReceiveProps are deprecated.
You can use static getDerivedStateFromProps and return a new state based on changes on props.
You don't have access to your this objects like props, so you cannot compare nextProps with your current props by nextProps.sth !== this.props.sth. You can compare you prevState value with nextProps and return new value of state.
Make sue you add UNSAFE_ to your current componentWillMount and the other deprecated lifecyle methods for now.
Use componentWillMount to get the data and set the state.
Then use componentWillReceiveProps for capturing update on the props.
You can check the Component Specs and Lifecycle.
I would use the render method. If the data is not loaded I would render a loader spinner and throw the action that fetch de data. For that i usually use the stores. Once the store has de data from the api, mark the data as loaded, throw an event and let the component get the data from the store, replacing the loader spinner with your data representation.
componentWillReceiveProps and other lifecycle methods seems like deceptive temptation to bring unnecessary complexity and noise to the code in the hands of inexperienced React coder. Why do they exist? What are their most typical use cases? In the moment of uncertainty, how would I know if the answer lies in the lifecycle methods?
I have been using react for couple of months now, and most of my work is creating a large application from scratch. So the same questions have presented themselves in the start.
The following information is based on learning while development and going through multiple docs out there to get it right.
As asked in the question here are couple of uses cases for the lifecycle methods in react
componentWillMount()
This is called once on the server side, if server side rendering is present, and once the client side.
I personally have used it just to do api calls which do not have direct effect on the components, for example getting oAuth tokens
componentDidMount()
This function is mostly used for calling API's (here is why to call it in componentDidMount and not in componentWillMount)
Components state initialisations which are based on the props passed by parents.
componentWillReceiveProps(nextProps,nextState)
This function is called every time props are received except the first render
Most common use I have encountered is to update the state of my current component which i can not do it in componentWillUpdate.
shouldComponentUpdate(nextProps, nextState)
This method is invoked before the render happens when new props or states are received. Here we can return false if the re-render is not required.
I see this as a performance optimisation tool. In case of frequent re-rendering of parent component this method should be used to avoid unnecessary update to current component
componentWillUpdate(nextProps,nextState)
this function is called every time a component is updated, it is not called when component mounts
Carry out any data processing here. For example, when a api fetch returns data, modelling the raw data into props to be passed to children
this.setState() is not allowed in this function , it is to be done in componentWillReceiveProps or componentDidUpdate
componentDidUpdate(prevProps,prevState)
Invoked right after the changes are pushed to the DOM
I have used it whenever the required data is not at the first render (waiting for api call to come through) and DOM requires to be changed based on the data received
Example, based on the age received show the user if he is eligible for application for an event
componentWillUnmount()
As the official docs mentions, any event listeners or timers used in the component to be cleaned here
In the moment of uncertainty, how would I know if the answer lies in
the lifecycle methods?
What analogy i suggest
Change is triggered in the component itself
Example, Enable editing of fields on click of an edit button
A function in the same component changes the state no involvement of lifecycle functions
Change is triggered outside of the component
Example, api call finished , need to display the received data
Lifecycle methods for the win.
Here are some more scenarios -
Does the change in state/props requires the DOM to be modified?
Example, if the current email is already present , give the input class an error class.
componentDidUpdate
Does the change in state/props requires to data to be updated?
Example, parent container which formats data received after api call and passes the formatted data to children.
componentWillUpdate
Props being passed to a child are changed , child needs to update
Example,
shouldComponentUpdate
Adding an event listener
Example, add a listener to monitor the DOM, based on window size.
componentDidMount
'componentWillMount' , to destroy the listner
Call api
'componentDidMount'
Sources -
Docs - https://facebook.github.io/react/docs/component-specs.html
this scotch.io article which cleared the lifecycle concepts
Event Listener - https://facebook.github.io/react/tips/dom-event-listeners.html
Some typical use cases for the most commonly used lifecycle methods:
componentWillMount: Invoked before initial rendering. Useful for making AJAX calls. For instance, if you need to grab the user information to populate the view, this is a good place to do it. If you do have an AJAX call, it would be good to render an indeterminate loading bar until the AJAX call finishes. I've also used componentWillMount to call setInterval and to disable Chrome's drag and drop functionality before the page renders.
componentDidMount: Invoked immediately after the component renders. Useful if you need to have access to a DOM element. For instance I've used it to disable copy and pasting into a password input field. Great for debugging if you need want to know the state of the component.
componentWillReceiveProps: Invoked when component receives new props. Useful for setting the state with the new props without re-rendering.
componentWillReceiveProps is part of Update lifce cycle methods and is called before rendering begins. The most obvious example is when new props are passed to a Component. For example, we have a Form Component and a Person Component. The Form Component has a single that allows the user to change the name by typing into the input. The input is bound to the onChange event and sets the state on the Form. The state value is then passed to the Person component as a prop.
import React from 'react';
import Person from './Person';
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = { name: '' } ;
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ name: event.currentTarget.value });
}
render() {
return (
<div>
<input type="text" onChange={ this.handleChange } />
<Person name={ this.state.name } />
</div>
);
}
}
Any time the user types into the this begins an Update for the Person component. The first method called on the Component is componentWillReceiveProps(nextProps) passing in the new prop value. This allows us to compare the incoming props against our current props and make logical decisions based on the value. We can get our current props by calling this.props and the new value is the nextProps argument passed to the method.
I know that an error is thrown when setting state for a component not yet mounted. Which explains the error I get from using setState function as oppose to explicitly and directly setting the state.
import React, {Component} from 'react';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {term: ''}; // -> seems to be the agreed means to set initial state
// this.setState({term: ''}); // -> generates an error
}
render() {
return (
<div>
<input onChange={event => this.setState({term: event.target.value})}/>
Value of the input: {this.state.term}
</div>
);
}
}
The error I get when I uncomment the second line this.setState({term: ''}) is:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the component.
I know how to prevent the error, simply by setting state explicitly without telling React anything about it and I have already seen the github issue talking about the bug: Github Issue #3878 What I want to know though is why cant React work it out? if one calls setState from a constructor it knows this is the first time its being used? I am probably simplifying it way too much, but if anyone has a nice technical answer as the reason why not?
React classes have always initialized with a property called state set to a value of null as seen in the source code. As you know, React provides a setState method for manipulating this property. According to the docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate().
In short, setState() is an asynchronous, multi-step, unpredictable operation that will cause component rerenders. To call such a function would require an object to already be fully initialized and thus couldn't happen while the class is still mounting. It would already try to perform lifecycle operations on the class before it was fully initialized.
Of course, that leaves an issue if you want a component to start out with a state that is not null and yet don't want to immediately cause multiple renders and operations to take place. That is why React provides a way to initialize a component state without relying on setState. In ES5, this was setting the initial state inside of a property called getInitialState. However, ES6 introduced a native syntax for setting properties when a class is initialized with a special constructor method (so React no longer had need of its own custom version). That is why, if you want to initialize a React component with a state when it is mounting, you must declare it as this.state = {} and not use setState().