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.4static 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.4static 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.jsexport const AppContext = React.createContext({ count: 0});const withApp = (Component) => (props) => ( <Consumer> {value => <Component {...props} count={value.count}/>} </Consumer>);// CountButton.jsconst {Consumer} = AppContext;// @withApp Decorator in ES2016class CountButton extends Component { render() { return( <button type="button" {...this.props}>{this.props.count}</button> ); }});export default = withApp(CountButton);// App.jsconst {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
//stringrender(){ return 'hello,world'} //numberrender(){ return 12345} //booleanrender(){ return isTrue?true:false} //nullrender(){ 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 itreturn ( <div> {this.props.children} </div>);
const modalRoot = document.getElementById('modal-root');// Modal.jsconstructor(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.