React 16 Upgrade
New Lifecycle
Mounting
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
- UNSAFE_componentWillMount()
Updating
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- UNSAFE_componentWillReceiveProps()
- UNSAFE_componentWillUpdate()
Previously, it was only called if the component was re-rendered by its parent,
and would not fire as the result of a local setState.
// Ok in react 16.3 but Bug in react16.4
static getDerivedStateFromProps(props, state) {
if (props.value !== state.controlledValue) {
return {
// Since this method fires on both props and state changes, local updates
// to the controlled value will be ignored, because the props version
// always overrides it. Oops!
controlledValue: props.value,
};
}
return null;
}
// Fixed in react16.4
static getDerivedStateFromProps(props, state) {
const prevProps = state.prevProps;
// Compare the incoming prop to previous prop
const controlledValue =
prevProps.value !== props.value
? props.value
: state.controlledValue;
return {
// Store the previous props in state
prevProps: props,
controlledValue,
};
}
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
Unmounting
- componentWillUnmount
Error Handling
- componentDidCatch
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Then you can use it as a regular component:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Context API
API
- React.createContext()
- Provider
- Consumer
// AppContext.js
export const AppContext = React.createContext({
count: 0
});
const withApp = (Component) => (props) => (
<Consumer>
{value => <Component {...props} count={value.count}/>}
</Consumer>
);
// CountButton.js
const {Consumer} = AppContext;
// @withApp Decorator in ES2016
class CountButton extends Component {
render() {
return(
<button type="button" {...this.props}>{this.props.count}</button>
);
}
});
export default = withApp(CountButton);
// App.js
const {Provider} = AppContext;
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
add = () => {
this.setState({
count: this.state.count + 1
});
};
render() {
return (
<div>
<Provider value={this.state}>
<CountButton onClick={this.add}/>
</Provider>
<CountButton onClick={this.add}/>
</div>
);
}
}
New Render Types
//string
render(){
return 'hello,world'
}
//number
render(){
return 12345
}
//boolean
render(){
return isTrue?true:false
}
//null
render(){
return null
}
//arrays ,need key . (fragments)
render(){
return [
<div>hello</div>,
<span>world</span>,
<p>oh</p>
]
}
//portals
...
Fragments
A common pattern in React is for a component to return multiple elements.
Fragments let you group a list of children without adding extra nodes to the DOM.
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
Portals
// React mounts a new div and renders the children into it
return (
<div>
{this.props.children}
</div>
);
const modalRoot = document.getElementById('modal-root');
// Modal.js
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
CreateRef & ForwardRef
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref}
className="FancyButton"
onClick={props.onClick}>
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleClick = () => {
// ref.current -> dom
this.myRef.current.focus();
};
render() {
return (
<FancyButton ref={this.myRef}
onClick={() => console.log(this.myRef.current)}>
Click me!
</FancyButton>
);
}
}
// <button class="FancyButton">Click me!</button>
SetState()
- Calling setState with null no longer triggers an update.
- Calling setState directly in render always causes an update.
- setState callbacks (second argument) now fire immediately after componentDidMount / componentDidUpdate instead of after all components have rendered.
DOM Attributes
In the past, React used to ignore unknown DOM attributes.
Now, any unknown attributes will end up in the DOM.
// Your code:
<div mycustomattribute="something" />
// React 15 output:
<div />
// React 16 output:
<div mycustomattribute="something" />
You should still use the canonical React naming for known attributes.
// Yes, please
<div tabIndex="-1" />
// Warning: Invalid DOM property `tabindex`. Did you mean `tabIndex`?
<div tabindex="-1" />
Pointer Events (React 16.4)
The following event types are now available in React DOM:
- onPointerDown
- onPointerMove
- onPointerUp
- ……
These events will only work in browsers that support the Pointer Events specification.