Deep Dive on the React Lifecycle
@jcreamer898 http:/ /bit.ly/react-lifecycle 1
Deep Dive on the React Lifecycle @jcreamer898 http:/ - - PowerPoint PPT Presentation
Deep Dive on the React Lifecycle @jcreamer898 http:/ /bit.ly/react-lifecycle 1 whoami Jonathan Creamer @jcreamer898 http:/ /bit.ly/react-lifecycle 2 whoami Currently Senior Front End Engineer at Lonely Planet Past JavaScript
@jcreamer898 http:/ /bit.ly/react-lifecycle 1
@jcreamer898 http:/ /bit.ly/react-lifecycle 2
@jcreamer898 http:/ /bit.ly/react-lifecycle 3
Agenda
@jcreamer898 http:/ /bit.ly/react-lifecycle 4
Pure Functions
@jcreamer898 http:/ /bit.ly/react-lifecycle 5
Can you even Component bro? function HelloWorld({ text }) { return ( <h1>{text}</h1> ); } ReactDOM.render(<HelloWorld text="hello world" />, document.body);
@jcreamer898 http:/ /bit.ly/react-lifecycle 6
JSX to JS
function HelloWorld(_ref) { var text = _ref.text; return React.createElement( "h1", null, text ); }
@jcreamer898 http:/ /bit.ly/react-lifecycle 7
class HelloWorld extends React.Component { render() { const { text } = this.props; return ( <h1>{text}</h1> ); } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 8
@jcreamer898 http:/ /bit.ly/react-lifecycle 9
Mounting
@jcreamer898 http:/ /bit.ly/react-lifecycle 10
Updating
@jcreamer898 http:/ /bit.ly/react-lifecycle 11
Unmounting
@jcreamer898 http:/ /bit.ly/react-lifecycle 12
@jcreamer898 http:/ /bit.ly/react-lifecycle 13
constructor aka init
@jcreamer898 http:/ /bit.ly/react-lifecycle 14
State
@jcreamer898 http:/ /bit.ly/react-lifecycle 15
setState class WYSIWYG extends React.Component { update() { /*...*/ } render() { const { text } = this.state; return ( <textarea
value={text} /> ); } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 16
setState
constructor(props) { super(props); this.state = { text: "", }; this.update = this.update.bind(this); } update(e) { const text = e.target.value; this.setState({ text }); }
@jcreamer898 http:/ /bit.ly/react-lifecycle 17
async setState
@jcreamer898 http:/ /bit.ly/react-lifecycle 18
@jcreamer898 http:/ /bit.ly/react-lifecycle 19
componentWillMount
@jcreamer898 http:/ /bit.ly/react-lifecycle 20
@jcreamer898 http:/ /bit.ly/react-lifecycle 21
componentDidMount
@jcreamer898 http:/ /bit.ly/react-lifecycle 22
componentDidMount import ace from "aceeditor"; export default class Editor extends React.Component { constructor(props) { super(props); this.state = { /* init state */ }; } componentDidMount() { this.editor = ace.edit(this.$text); } render() { return ( <div ref={(node) => this.$text = node} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 23
componentDidMount import ace from "aceeditor"; export default class Editor extends React.Component { constructor(props) { super(props); this.state = { /* init state */ }; } componentDidMount() { this.editor = ace.edit(this.$text); } render() { return ( <div ref={(node) => this.$text = node} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 24
componentDidMount import ace from "aceeditor"; export default class Editor extends React.Component { constructor(props) { super(props); this.state = { /* init state */ }; } componentDidMount() { this.editor = ace.edit(this.$text); } render() { return ( <div ref={(node) => this.$text = node} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 25
componentDidMount import ace from "aceeditor"; export default class Editor extends React.Component { constructor(props) { super(props); this.state = { /* init state */ }; } componentDidMount() { this.editor = ace.edit(this.$text); } render() { return ( <div ref={(node) => this.$text = node} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 26
@jcreamer898 http:/ /bit.ly/react-lifecycle 27
Be a good citizen
@jcreamer898 http:/ /bit.ly/react-lifecycle 28
componentWillUnmount
class Chat extends Component { constructor(props) { super(props); this.state = { messages: [] }; } componentDidMount() { this.subscription = postal.subscribe({ topic: "message.added", callback: (message) => { this.setState({ messages: [...this.state.messages, message] }) } }); } componentWillUmount() { this.subscription.unsubscribe(); } render() { return ( <Messages messages={this.state.message} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 29
componentWillUnmount
class Chat extends Component { constructor(props) { super(props); this.state = { messages: [] }; } componentDidMount() { this.subscription = postal.subscribe({ topic: "message.added", callback: (message) => { this.setState({ messages: [...this.state.messages, message] }) } }); } componentWillUmount() { this.subscription.unsubscribe(); } render() { return ( <Messages messages={this.state.message} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 30
componentWillUnmount
class Chat extends Component { constructor(props) { super(props); this.state = { messages: [] }; } componentDidMount() { this.subscription = postal.subscribe({ topic: "message.added", callback: (message) => { this.setState({ messages: [...this.state.messages, message] }) } }); } componentWillUmount() { this.subscription.unsubscribe(); } render() { return ( <Messages messages={this.state.message} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 31
componentWillUnmount
class Chat extends Component { constructor(props) { super(props); this.state = { messages: [] }; } componentDidMount() { this.subscription = postal.subscribe({ topic: "message.added", callback: (message) => { this.setState({ messages: [...this.state.messages, message] }) } }); } componentWillUmount() { this.subscription.unsubscribe(); } render() { return ( <Messages messages={this.state.message} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 32
componentWillUnmount
class Chat extends Component { constructor(props) { super(props); this.state = { messages: [] }; } componentDidMount() { this.subscription = postal.subscribe({ topic: "message.added", callback: (message) => { this.setState({ messages: [...this.state.messages, message] }) } }); } componentWillUmount() { this.subscription.unsubscribe(); } render() { return ( <Messages messages={this.state.message} /> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 33
@jcreamer898 http:/ /bit.ly/react-lifecycle 34
IRL
@jcreamer898 http:/ /bit.ly/react-lifecycle 35
componentDidMount IRL
export default class PoiDetail extends React.PureComponent { componentDidMount() { if (!this.props.poi) { this.fetchPoi(this.props.params.id); } if (!this.props.related) { this.fetchRelated(this.props.params.id); } } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 36
@jcreamer898 http:/ /bit.ly/react-lifecycle 37
I before E...
@jcreamer898 http:/ /bit.ly/react-lifecycle 38
componentWillReceiveProps
@jcreamer898 http:/ /bit.ly/react-lifecycle 39
componentWillReceiveProps
class AvatarUploader extends Component { constructor(props) { super(props); this.state = { src: props.src }; } componentWillReceiveProps(nextProps) { if (nextProps.src !== this.props.src) { this.setState({ src: nextProps.src, }); } } uploadFiles() { this.props.upload() } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 40
componentWillReceiveProps
@jcreamer898 http:/ /bit.ly/react-lifecycle 41
componentWillReceiveProps
class Editor extends PureComponent { constructor(props) { /*... */ } upload(files) { this.props.dispatch(uploadAction(files)); } render() { const { image } = this.props; return ( <AvatarUploader src={image} upload={this.upload} /> ) } } const mapStateToProps = (state) => ({ image: state.image }) export default connect(mapStateToProps)(Editor);
@jcreamer898 http:/ /bit.ly/react-lifecycle 42
React Router
class SightComponent extends Component { render() { return ( <div> <Link to="/a/poi-sig/381139/362228">Country Music Hall of Fame</Link> </div> ) } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 43
React Router
componentWillReceiveProps(nextProps) { const { id: currentId } = this.props.params; const { id: nextId } = nextProps.params; if (currentId !== nextId) { this.props.fetchPoi(nextId); } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 44
lOOps
componentWillReceiveProps(nextProps) { const { id: currentId } = this.props.params; const { id: nextId } = nextProps.poi; if (currentId !== nextId) { this.props.fetchPoi(nextId); } if (this.props.poi !== nextProps.poi) { // W/ redux, this is probably always true! } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 45
loops
@jcreamer898 http:/ /bit.ly/react-lifecycle 46
@jcreamer898 http:/ /bit.ly/react-lifecycle 47
shouldComponentUpdate
shouldComponentUpdate(this, nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } export class Counter extends React.PureComponent { // ... }
@jcreamer898 http:/ /bit.ly/react-lifecycle 48
shouldComponentUpdate
@jcreamer898 http:/ /bit.ly/react-lifecycle 49
@jcreamer898 http:/ /bit.ly/react-lifecycle 50
componentWillUpdate
@jcreamer898 http:/ /bit.ly/react-lifecycle 51
@jcreamer898 http:/ /bit.ly/react-lifecycle 52
componentDidUpdate
componentDidUpdate(prevProps) { if (prevProps.poi.id !== this.poi.id) { window.scrollTop = 0; this.props.pageView(); } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 53
@jcreamer898 http:/ /bit.ly/react-lifecycle 54
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 55
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 56
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 57
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 58
React Router
@jcreamer898 http:/ /bit.ly/react-lifecycle 59
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 60
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 61
@jcreamer898 http:/ /bit.ly/react-lifecycle 62
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 63
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 64
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 65
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 66
export default class PoiDetail extends React.PureComponent { constructor(props) { // ... } hasPoiUpdated() {} componentDidMount() { this.subscription = postal.subscribe({ /* ... */ }) } componentWillReceiveProps(nextProps) { if (nextProps.params.id !== this.props.params.id) { this.props.fetchPoi(nextProps.params.id); } } shouldComponentUpdate(nextProps) { return nextProps.poi.id !== this.props.poi.id; } componentWillUpdate(nextProps, nextState) { /* ... */ } componentDidUpdate(prevProps) { const isNewPoi = prevProps.poi.id !== this.props.poi.id; if (isNewPoi) { window.scrollTop = 0; } } componentWillUnmount() { this.subscription.unsubscribe(); } render() { /* ... */ } }
@jcreamer898 http:/ /bit.ly/react-lifecycle 67
@jcreamer898 http:/ /bit.ly/react-lifecycle 68
Name of thing Sorta like... Mounted? Can you even setState? What would you say... ya do here? constructor initialize() nope nope init stuff NO side effects componentWillMount beforeDomReady() nope yeah but don't Only needed in createClass now use constructor for most things render render nope please no render stuff and don't set any state please componentDidMount domReady() yup yup DOM is a go init jQuery plugins dispatch stuff componentWillReceivePro ps
yup yup Props changed feel free to update state if needed componentWillUpdate beforeRender() yup nope The props or state changed need to do anything else before rendering? shouldComponentUpdate shouldRender() yup nope So yeah something changed but do we REALLY need to update? componentDidUpdate a"erRender() yup yup Great success we've rendered a thing... anything else? componentWillUnmount destroy() too late too late Only you can prevent memory leaks aka unbind things @jcreamer898 http:/ /bit.ly/react-lifecycle 69
@jcreamer898 http:/ /bit.ly/react-lifecycle 70
@jcreamer898 http:/ /bit.ly/react-lifecycle 71
@jcreamer898 http:/ /bit.ly/react-lifecycle 72
Testing
@jcreamer898 http:/ /bit.ly/react-lifecycle 73
Testing willMount
describe("Detail Page", () => { it("should fetch a page if there isn't one loaded", () => { const fetch = jest.fn(); const wrapper = mount( <Details poi={null} fetchPoi={fetch} /> ); expect(fetch).toHaveBeenCalled(); }); });
@jcreamer898 http:/ /bit.ly/react-lifecycle 74
Testing willReceiveProps
describe("Detail Page", () => { it("should fetch a new page", () => { const fetch = jest.fn(); const wrapper = mount( <Details poi={{ id: 1}} params={{ id: 1 }} fetchPoi={fetch} /> ); wrapper.setProps({ params: { id: 2 } }); expect(fetch).toHaveBeenCalled(); }); });
@jcreamer898 http:/ /bit.ly/react-lifecycle 75
Additional Resources
@jcreamer898 http:/ /bit.ly/react-lifecycle 76
@jcreamer898 jonathancreamer.com
@jcreamer898 http:/ /bit.ly/react-lifecycle 77