Solution: swap to PropTypes
- LazyInput
- problem: PropTypes, React.createClass
- doesn't appear to be maintained
- single file, Website repo only
- our solution: port file into Website repo
- @mapbox/range
- problem: PropTypes
- single file, Website repo only
- our solution: port file into Website repo
- Helmet updated from v3.1.0 to v5.0.0
- problem: PropTypes (despite source using them properly..)
- our solution: we don't really need this/can roll our own via React 16 Portals (more details below)
- Enzyme
* problem: our version is incompatible with React 16
* solution: update from v2.9.1 to 3.2.0
- install enzyme-adapter-react-16
- simplify mocha npm scripts with mocha.opts file
- configure adapter (like we do for EA)
- react-addons-css-transition-group
- problem: defunct
- solution: [email protected] is drop-in replacement
Our code before:
// Html.jsx
<script src={ `https://cdnjs.cloudflare.com/ajax/libs/react/${cdnFileVersion('react', 'react-with-addons')}` }></script>
<script src={ `https://cdnjs.cloudflare.com/ajax/libs/react/${cdnFileVersion('react-dom')}` }></script>
And after:
// Html.jsx
<script crossOrigin="true" src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossOrigin="true" src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
With Portals, we can attach components anywhere (via document.querySelector) in the DOM. Typical use case: modals/dialogs, tooltips -- anywhere a child element may need to break out of a parent container
Currently, we use react-helmet to let us sneak metadata into the proper location in the DOM (in head
) from different components. React Portals let us accomplish the same idea.
Portals work by attaching to a DOM -- meaning we're SoL when it comes to SSR. So we got creative; the same way we track which code-split chunks are used in each render, we track what portals are called, and "flush" them into the DOM during server render.
This article inspired our approach, but is a little magical: https://michalzalecki.com/render-react-portals-on-the-server/ - but to keep things simple, we're not using any third party packages.
// render.js
// Gather chunks that were sync loaded on the server
// so we can flush proper initial chunks to client
let chunkNames = []
const pushChunkName = chunkName => {
chunkNames = [ ...chunkNames, chunkName ]
}
let portals = []
const addPortalSSR = (children, selector) => {
portals = [ ...portals, { children, selector }]
}
<AppRoutes
pushChunkName={ pushChunkName }
addPortalSSR={ addPortalSSR }
/>
exposes both pushChunkName
and addPortalSSR
as functions in child context, so any server-rendered component can tap into the methods.
const Head = ({ children }, { addPortalSSR }) => {
if (addPortalSSR) {
addPortalSSR(children, 'head')
return null
}
return ReactDOM.createPortal(children, document.querySelector('head'))
}
Now we have the portals stashed in our portals
variable by the time we get to rendering the markup to string:
let html = renderToString(
<HtmlComponent
context={ componentContext }
state={ exposedState }
markup={ motherLoad }
chunkNames={ chunkNames }
/>,
)
Given our rendered-to-string markup, now all we have to do now is inject the portals in the appropriate containers before sending the markup to the client:
html = _appendPortalsToMarkup(portals, html) // uses cheero library for server-side DOM manipulation, similar to jQuery
res.status(200).send(`<!DOCTYPE html>${html}`)
- Adapter (like with EA)
expect
is now global variable (like with EA)- Test helpers file moved to setupMocha.js