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).