[React] Components life cycle, state, and props

Debug

Higher Order Components Wrap other components and throws errors when inner components has error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ErrorBoundary.js
import React, { Component } from 'react';

class ErrorBoundary extends Component {
state = {
hasError: false,
errorMessage: ''
}

componentDidCatch = (error, info) => {
this.setState({hasError: true, errorMessage: error});
}

render() {
if (this.state.hasError) {
return <h1>Something went wrong</h1>
} else {
return this.props.children;
}
}
}

export default ErrorBoundary;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// App.js
output = (
<div>
{this.state.persons.map((person, index) => {
return (
<ErrorBoundary key={person.id}>
<UserOutput
name={person.name}
click={() => this.deleteHandler(index)}
changed={(event) => this.eventHandler(event, person.id)}/>
</ErrorBoundary>)
})}
</div>
)

Stateful and Stateless Component

Change in state and props will trigger re-rendering of the DOM.

  • Stateful(Containers)

    • class XY extends Component
    • Access to State
    • Lifecycle Hooks
    • this.state.XY & this.props.XY
    • Use only if you need to manage State or access to Lifecycle Hooks
  • Stateless

    • const XY = (props) => {...}
    • Cannot Access to State
    • Do not have Lifecycle Hooks
    • props.XY
    • Use in all other cases

Component Lifecycle

Only available in Stateful Components

  • Creation of Components

    • constructor(props) create component, and instantiate. Always call super() and call props if we implement constructor()
      • Do not cause side-effects (re-render the application and reduce performance)
    • componentWillMount() won’t use much
    • render() Prepare & Structure your JSX code
    • Render Child Components
    • componentDidMount() Cause Side-Effects. Don’t update state (trigger re-render)
  • Update (triggered by Parent)

    • componentWillReceiveProps(nextProps) Sync State to the updated props. Don’t cause side-effects
    • shouldComponentUpdate(nextProps, nextState) May cancel updating process.
      • Do: Decide whether to continue or not
      • Don’t: Cause Side-Effects
    • componentWillUpdate
      • Do: Sync state to props
      • Don’t: Cause Side-Effects
    • render() Prepare & Structure your JSX code
    • Update Child Component Props
    • componentDidUpdate()
      • Do: Cause Side-Effects
      • Don’t: Update State (trigger re-render)
  • Update (triggered by Internal Change)

    • shouldComponentUpdate(nextProps, nextState) Decide whether to continue or not
    • componentWillUpdate() Sync State to Props
    • render() Prepare and Structure your JSX code
    • Update Child Component Props
    • componentDidUpdate() Cause side effects

How react upates the App & the component tree

When the render() is called, the DOM does not necessarily change.

  1. Compare Old Virtual DOM and the new re-rendered virtual DOM
  2. If differences are found, update the different part of the DOM (if shouldComponentUpdate() is passed)

setState Correctly

setState works asynchronously, thus it would be dangerous to directly use it as:

1
2
3
4
this.setState({
toggleClicked: this.state.toggleClicked + 1;
})
// Dangerous

Instead, we should use this:

1
2
3
4
5
6
7
toggleHandler() {
this.setState((prevState, props) => {
return {
toggleClicked: prevState.toggleClicked + 1
}
})
}

String props types

npm install --save prop-types

1
2
3
4
5
6
7
8
9
import PropTypes from 'prop-types';

Person.propTypes = {
click: PropTypes.func,
name: PropTypes.string,
age: PropTypes.number,
changed: PropTypes.func
}
export default Person;

Access Elements with ref

Can only be used in the stateful components

1
2
3
4
5
6
7
8
9
10
componentDidMount() {
this.inputElement.focus();
}
render() {
return (
<input
ref={(inp) => {this.inputElement = imp}} //
>
)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// After 16.3
class Person extends Component {
constructor (props) {
super(props);
this.inputElementRef = React.createRef();
}

componentDidMount() { // like init() in Angular ?
this.focusInput();
}

focusInput() {
this.inputElementRef.current.focus();
}

render() {
return (
<input
ref={this.inputElementRef} // connect ref to this element
>
)
}
}

Lifecycle Hooks

Creation

From top to bottom is the running sequence of lifecycle hooks

Syntax DO DON’T Other
constructor(props) Set up Initial State Cause Side-Effects Default ES6 class Feature
getDerivedStateFromProps(props,state) Sync state Cause Side-Effects not used much
render() Prepare & Structure JSX Code Cause Side-Effects
Render Child Component
componentDidMount() Cause Side-Effects Update State synchronisly(can update with HTTP) Very Important

Syntax

1
2
3
static getDerivedStateFromProps(props, state) {
return state;
}

componentWillMount() will be removed in the future.

Update (prop changes)

props changes are mostly for external data changes (i.e.HTTP request)

Syntax DO DON’T Other
getDerivedStateFromProps(props,state) Sync State to Props Cause Side-Effects
shouldComponentUpdate(nextProps, nextState) Decide whether to continue or not Cause Side-Effects May cancel updating process
render() Prepare & Structure JSX Code Cause Side-Effects
Update Child Component Props
getSnapshotBeforeUpdate(prevProps, prevState) Last-minute DOM ops Cause Side-Effects Not used much
componentDidUpdate() Cause Side-Effects Update State Synchronisly Beware of infinite loop

getStapshotBeforeUpdate Usage: can be used to save some data before the update and then use it later the upate.

1
2
3
4
5
6
7
getStapshotBeforeUpdate(prevProps, prevState) {
return {message: 'Snapshot' };
}

componentDidUpdate(prevProps, prevState, snapshot) {
console.log(snapshot); // {message: 'snapshot'}
}

componentWillReceiveProps and componentWillUpdate are removed, and shouldn’t be used.
componentWillUnmount runs right before the component is removed from the DOM.

Lifecycle hooks in Functional Components

useEffect combines all the componentDidMount() and componentDidUpdate()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useEffect } from 'react';

const cockpit = (props) => {
useEffect(() => { // Runs for every update
// Http request...
}, [props.persons]); // Only update when persons change

useEffect(() => {
}, []); // Run only once when the app start

useEffect(() => {
const timer = setTimeout(() => {
alert('Saved data to cloud');
}, 1000);
return () => {
clearTimeout(timer); // Cleanup
console.log('Happens right before component is removed');
}
}, []); // Run only once when the app start
}

Performance

Performance Gains

1
2
3
shouldComponentUpdate(nextProps, nextState) {
return nextProps.changed !== this.props.persons || nextProps.changed !== this.props.changed || nextProps.clicked !== this.propls.clicked; // Don't update if no props is changed
}

PureComponent has built-in shouldComponentUpdate() function, so it’s optimized to update the states only if it’s different from the old one. Thus, it can increase the performance in some occasions.

1
2
3
4
5
import { PureComponent } from 'react';

class SomeComponent extends PureComponent {
//...
}

After React 16.4, the stateless component can use memo method to achieve similar effects.

1
export default React.memo(component); // will only update when receives props update

Improve performance with shouldComponentUpdate() in class-based components.

1
2
3
4
5
6
7
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.persons !== this.props.persons) {
return true;
} else {
return false;
}
}

Improve performance for function-based components with react.memo(). This uses memoization to optimize the performance. It’s a good idea to wrap functional components that might not need to update with every change in the parent component.

1
2
3
const component = () => {...}

export default react.memo(component);

How React Update the DOM

shouldComponentUpdate() if true -> call render() -> compare old Virtual DOM with re-rendered Virtual DOM -> If there are differences -> Update real DOM