From 59f163adaffc316215cfdf2e16e06887ef188c62 Mon Sep 17 00:00:00 2001 From: Danry Ague <74456102+decanTyme@users.noreply.github.com> Date: Sat, 9 Apr 2022 07:40:22 +0800 Subject: [PATCH] feat!: Migrate to `wrapPageElement` Gatsby API * Rewrite `ReplaceComponentRenderer` to functional component BREAKING CHANGE: Removes support for `replaceComponentRenderer` API from Gatsby v2. --- gatsby-browser.js | 6 +- src/ModalRoutingContext.js | 6 +- src/PageElementWrapper.jsx | 133 +++++++++++++++++++++++++++++ src/onClientEntry.js | 2 +- src/replaceComponentRenderer.js | 146 -------------------------------- 5 files changed, 138 insertions(+), 155 deletions(-) create mode 100644 src/PageElementWrapper.jsx delete mode 100644 src/replaceComponentRenderer.js diff --git a/gatsby-browser.js b/gatsby-browser.js index 8a5d249..aab35b5 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,9 +1,7 @@ -const { - default: replaceComponentRenderer, -} = require('./dist/replaceComponentRenderer') +const { default: wrapPageElement } = require('./dist/PageElementWrapper') const { default: shouldUpdateScroll } = require('./dist/shouldUpdateScroll') const { default: onClientEntry } = require('./dist/onClientEntry') exports.onClientEntry = onClientEntry -exports.replaceComponentRenderer = replaceComponentRenderer +exports.wrapPageElement = wrapPageElement exports.shouldUpdateScroll = shouldUpdateScroll diff --git a/src/ModalRoutingContext.js b/src/ModalRoutingContext.js index a32ad59..940ca47 100644 --- a/src/ModalRoutingContext.js +++ b/src/ModalRoutingContext.js @@ -1,10 +1,8 @@ import React from 'react' -export const defaultValue = { +const ModalRoutingContext = React.createContext({ isModal: false, closeTo: null, -} - -const ModalRoutingContext = React.createContext(defaultValue) +}) export default ModalRoutingContext diff --git a/src/PageElementWrapper.jsx b/src/PageElementWrapper.jsx new file mode 100644 index 0000000..025e6df --- /dev/null +++ b/src/PageElementWrapper.jsx @@ -0,0 +1,133 @@ +import { navigate } from 'gatsby' +import React from 'react' +import Modal from 'react-modal' +import ModalRoutingContext from './ModalRoutingContext' + +const withoutPrefix = (path) => { + const prefix = + typeof __BASE_PATH__ !== `undefined` ? __BASE_PATH__ : __PATH_PREFIX__ + + return path.slice(prefix ? prefix.length : 0) +} + +const element = {} + +function PageElementWrapper({ props, opts }) { + const modalContentRef = React.useRef(null) + const [state, setState] = React.useState({ + prevProps: null, + lastModalProps: null, + props: null, + pathname: null, + }) + + const handleRequestClose = () => { + navigate(withoutPrefix(state.prevProps.location.pathname), { + state: { + noScroll: true, + }, + }) + } + + React.useEffect(() => { + if ( + state.prevProps?.location.pathname !== props.location.pathname && + props.location.state.modal && + modalContentRef.current + ) { + modalContentRef.current.scrollTop = 0 + } + }) + + if (state.pathname !== props?.location.pathname) { + setState({ + pathname: props?.location.pathname, + props, + ...(state.props?.location.state.modal + ? { + // Old page was a modal, keep track so we + // can render the contents while closing + lastModalProps: state.props, + } + : { + // Old page was not a modal, keep track so + // we can render the contents under modals + prevProps: state.props, + }), + }) + } + + const isModal = state.prevProps && props.location.state.modal + + const resources = isModal + ? state.prevProps.pageResources + : props.pageResources + + // The page is the previous path if this is a modal, + // otherwise it's the current path + element.page = React.createElement(resources.component, { + ...(isModal ? state.prevProps : props), + key: resources.page.path, + }) + + if (isModal) { + // Rendering the current page as a modal, + // so create an element with the page contents + element.modal = React.createElement(props.pageResources.component, { + ...props, + key: props?.pageResources.page.path, + }) + } else if (state.lastModalProps) { + // Not rendering the current page as a modal, but we may + // be in the process of animating the old modal content + // to close, so render the last modal content we have cached + element.modal = React.createElement( + state.lastModalProps.pageResources.component, + { + ...state.lastModalProps, + key: state.lastModalProps.pageResources.page.path, + } + ) + } + + const modalValues = React.useMemo( + () => ({ + modal: isModal, + closeTo: state.prevProps + ? withoutPrefix(state.prevProps.location.pathname) + : '/', + }), + [isModal, state.prevProps] + ) + + return ( + <> + {element.page} + + { + modalContentRef.current = node + }} + {...opts?.modalProps} + isOpen={Boolean(isModal)} + > + {element.modal ? ( + + + {element.modal} + + + ) : null} + + + ) +} + +const wrapPageElement = (context, opts) => + React.createElement(PageElementWrapper, { + ...context, + opts, + }) + +export default wrapPageElement diff --git a/src/onClientEntry.js b/src/onClientEntry.js index e9adb7c..8a1d051 100644 --- a/src/onClientEntry.js +++ b/src/onClientEntry.js @@ -1,6 +1,6 @@ import Modal from 'react-modal' -const onClientEntry = (_args, opts = {}) => { +const onClientEntry = (_, opts) => { const { appElement = `#___gatsby` } = opts Modal.setAppElement(appElement) } diff --git a/src/replaceComponentRenderer.js b/src/replaceComponentRenderer.js deleted file mode 100644 index 5174926..0000000 --- a/src/replaceComponentRenderer.js +++ /dev/null @@ -1,146 +0,0 @@ -import _ from 'lodash' -import { navigate } from 'gatsby' -import React from 'react' -import Modal from 'react-modal' -import ModalRoutingContext from './ModalRoutingContext' - -const withoutPrefix = (path) => { - const prefix = - typeof __BASE_PATH__ !== `undefined` ? __BASE_PATH__ : __PATH_PREFIX__ - - return path.slice(prefix ? prefix.length : 0) -} - -class ReplaceComponentRenderer extends React.Component { - state = { - prevProps: null, - lastModalProps: null, - props: null, - pathname: null, - } - - modalContentRef = null - - constructor(...args) { - super(...args) - } - - static getDerivedStateFromProps(props, state) { - // TODO: handle history changes - if (props.location.pathname !== state.pathname) { - return { - pathname: props.location.pathname, - props: props, - ...(_.get(state, 'props.location.state.modal') - ? { - // old page was a modal, keep track so we can render the contents while closing - lastModalProps: state.props, - } - : { - // old page was not a modal, keep track so we can render the contents under modals - prevProps: state.props, - }), - } - } - - return null - } - - componentDidUpdate(prevProps) { - if ( - _.get(prevProps, 'location.pathname') !== - _.get(this.props, 'location.pathname') && - _.get(this.props, 'location.state.modal') && - this.modalContentRef - ) { - this.modalContentRef.scrollTop = 0 - } - } - - handleRequestClose = () => { - navigate(withoutPrefix(this.state.prevProps.location.pathname), { - state: { - noScroll: true, - }, - }) - } - - render() { - // render modal if props location has modal - const { pageResources, location, modalProps } = this.props - const { prevProps, lastModalProps } = this.state - const isModal = prevProps && _.get(location, 'state.modal') - - const resources = isModal ? prevProps.pageResources : pageResources - - // the page is the previous path if this is a modal, otherwise it's the current path - const pageElement = isModal - ? React.createElement(prevProps.pageResources.component, { - ...prevProps, - key: prevProps.pageResources.page.path, - }) - : React.createElement(pageResources.component, { - ...this.props, - key: pageResources.page.path, - }) - - let modalElement = null - - if (isModal) { - // Rendering the current page as a modal, so create an element with the page contents - modalElement = React.createElement(pageResources.component, { - ...this.props, - key: pageResources.page.path, - }) - } else if (lastModalProps) { - // Not rendering the current page as a modal, but we may be in the process of animating - // the old modal content to close, so render the last modal content we have cached - - modalElement = React.createElement( - _.get(lastModalProps, 'pageResources.component'), - { - ...lastModalProps, - key: _.get(lastModalProps, 'pageResources.page.path'), - } - ) - } - - return ( - <> - {pageElement} - - (this.modalContentRef = node)} - {...modalProps} - isOpen={!!isModal} - > - {modalElement ? ( - - - {modalElement} - - - ) : null} - - - ) - } -} - -const replaceComponentRenderer = ({ props }, opts) => { - const { modalProps } = opts - return React.createElement(ReplaceComponentRenderer, { - ...props, - modalProps, - }) -} - -export default replaceComponentRenderer