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)
expectis now global variable (like with EA)- Test helpers file moved to setupMocha.js