From 3a82af3689efcf53e3a1d65fdc374975423f90d1 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 10 Aug 2018 00:38:06 +0200 Subject: [PATCH] [NoSrr] Add a defer property --- docs/src/pages/utils/no-ssr/no-ssr.md | 11 ++++++ packages/material-ui/src/NoSsr/NoSsr.js | 35 +++++++++++++++++--- packages/material-ui/src/NoSsr/NoSsr.test.js | 20 ++++++++++- pages/api/no-ssr.md | 3 +- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/docs/src/pages/utils/no-ssr/no-ssr.md b/docs/src/pages/utils/no-ssr/no-ssr.md index 74b6c356234731..03dd2582c3931e 100644 --- a/docs/src/pages/utils/no-ssr/no-ssr.md +++ b/docs/src/pages/utils/no-ssr/no-ssr.md @@ -12,5 +12,16 @@ This component can be useful in a variety of situations: - Improve the time-to-first paint on the client by only rendering above the fold. - Reduce the rendering time on the server. - Under too heavy server load, you can turn on service degradation. +- Improve the time-to-interactive by only rendering what's important (with the `defer` property). + +## Client side deferring {{"demo": "pages/utils/no-ssr/SimpleNoSsr.js"}} + +## Frame deferring + +In it's core, the NoSsr component purpose is to **defer rendering**. +As it's illustrated in the previous demo, you can use it to defer the rendering from the server to the client. + +But you can also use it to defer the rendering within the client itself. +You can **wait a screen frame** with the `defer` property to render the children. diff --git a/packages/material-ui/src/NoSsr/NoSsr.js b/packages/material-ui/src/NoSsr/NoSsr.js index dd33fe28e596e9..47666bd8b310ff 100644 --- a/packages/material-ui/src/NoSsr/NoSsr.js +++ b/packages/material-ui/src/NoSsr/NoSsr.js @@ -2,8 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import exactProp from '../utils/exactProp'; -const Fallback = () => null; - /** * NoSsr purposely removes components from the subject of Server Side Rendering (SSR). * @@ -19,7 +17,27 @@ class NoSsr extends React.Component { }; componentDidMount() { - this.setState({ mounted: true }); // eslint-disable-line react/no-did-mount-set-state + this.mounted = true; + + if (this.props.defer) { + // Wondering why we use two raf? Check this video out: + // https://www.youtube.com/watch?v=cCOL7MC4Pl0 + requestAnimationFrame(() => { + // The browser should be about to render the DOM that React commited at this point. + // We don't want to interrupt. Let's wait the next raf. + requestAnimationFrame(() => { + if (this.mounted) { + this.setState({ mounted: true }); + } + }); + }); + } else { + this.setState({ mounted: true }); // eslint-disable-line react/no-did-mount-set-state + } + } + + componentWillUnmount() { + this.mounted = false; } render() { @@ -31,13 +49,22 @@ class NoSsr extends React.Component { NoSsr.propTypes = { children: PropTypes.node.isRequired, + /** + * If `true`, the component will not only prevent server side rendering. + * It will also defer the rendering of the children into a different screen frame. + */ + defer: PropTypes.bool, + /** + * The fallback content to display. + */ fallback: PropTypes.node, }; NoSsr.propTypes = exactProp(NoSsr.propTypes); NoSsr.defaultProps = { - fallback: , + defer: false, + fallback: null, }; export default NoSsr; diff --git a/packages/material-ui/src/NoSsr/NoSsr.test.js b/packages/material-ui/src/NoSsr/NoSsr.test.js index 54cb8ef08d412c..b99f71aea3555e 100644 --- a/packages/material-ui/src/NoSsr/NoSsr.test.js +++ b/packages/material-ui/src/NoSsr/NoSsr.test.js @@ -25,7 +25,7 @@ describe('', () => { Hello , ); - assert.strictEqual(wrapper.name(), 'Fallback'); + assert.strictEqual(wrapper.name(), null); }); }); @@ -37,6 +37,7 @@ describe('', () => { , ); assert.strictEqual(wrapper.find('span').length, 1); + assert.strictEqual(wrapper.text(), 'Hello'); }); }); @@ -50,4 +51,21 @@ describe('', () => { assert.strictEqual(wrapper.text(), 'fallback'); }); }); + + describe('prop: defer', () => { + it('should defer the rendering', done => { + const wrapper = mount( + + Hello + , + ); + assert.strictEqual(wrapper.find('span').length, 0); + setTimeout(() => { + wrapper.update(); + assert.strictEqual(wrapper.find('span').length, 1); + assert.strictEqual(wrapper.text(), 'Hello'); + done(); + }, 100); + }); + }); }); diff --git a/pages/api/no-ssr.md b/pages/api/no-ssr.md index d7835a40dd5bd6..6c554d7314144e 100644 --- a/pages/api/no-ssr.md +++ b/pages/api/no-ssr.md @@ -22,7 +22,8 @@ This component can be useful in a variety of situations: | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| | children * | node |   | | -| fallback | node | <Fallback /> | | +| defer | bool | false | If `true`, the component will not only prevent server side rendering. It will also defer the rendering of the children into a different screen frame. | +| fallback | node | null | The fallback content to display. | Any other properties supplied will be spread to the root element (native element).