diff --git a/packages/sui-react-initial-props/src/createClientContextFactoryParams.ts b/packages/sui-react-initial-props/src/createClientContextFactoryParams.js similarity index 63% rename from packages/sui-react-initial-props/src/createClientContextFactoryParams.ts rename to packages/sui-react-initial-props/src/createClientContextFactoryParams.js index 5f69702f7..2403c5e5f 100644 --- a/packages/sui-react-initial-props/src/createClientContextFactoryParams.ts +++ b/packages/sui-react-initial-props/src/createClientContextFactoryParams.js @@ -1,6 +1,4 @@ -import { ContextFactoryParams } from './types' - -export default (): ContextFactoryParams => ({ +export default () => ({ appConfig: window.__APP_CONFIG__, cookies: document.cookie, isClient: true, diff --git a/packages/sui-react-initial-props/src/createServerContextFactoryParams.ts b/packages/sui-react-initial-props/src/createServerContextFactoryParams.js similarity index 63% rename from packages/sui-react-initial-props/src/createServerContextFactoryParams.ts rename to packages/sui-react-initial-props/src/createServerContextFactoryParams.js index 5012976bf..d49f9f812 100644 --- a/packages/sui-react-initial-props/src/createServerContextFactoryParams.ts +++ b/packages/sui-react-initial-props/src/createServerContextFactoryParams.js @@ -1,6 +1,4 @@ -import { ContextFactoryParams } from './types' - -export default (req: IncomingMessage.ServerRequest): ContextFactoryParams => ({ +export default req => ({ appConfig: req.appConfig, req, cookies: req.headers.cookie, diff --git a/packages/sui-react-initial-props/src/index.js b/packages/sui-react-initial-props/src/index.js new file mode 100644 index 000000000..63cd26114 --- /dev/null +++ b/packages/sui-react-initial-props/src/index.js @@ -0,0 +1,4 @@ +export {default as createClientContextFactoryParams} from './createClientContextFactoryParams.js' +export {default as createServerContextFactoryParams} from './createServerContextFactoryParams.js' +export {default as loadPage} from './loadPage.js' +export {default as ssrComponentWithInitialProps} from './ssrComponentWithInitialProps.js' diff --git a/packages/sui-react-initial-props/src/index.ts b/packages/sui-react-initial-props/src/index.ts deleted file mode 100644 index f043a5c6d..000000000 --- a/packages/sui-react-initial-props/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as createClientContextFactoryParams } from './createClientContextFactoryParams' -export { default as createServerContextFactoryParams } from './createServerContextFactoryParams' -export { default as loadPage } from './loadPage' -export { default as ssrComponentWithInitialProps } from './ssrComponentWithInitialProps' diff --git a/packages/sui-react-initial-props/src/initialPropsContext.js b/packages/sui-react-initial-props/src/initialPropsContext.js new file mode 100644 index 000000000..b37eb74f3 --- /dev/null +++ b/packages/sui-react-initial-props/src/initialPropsContext.js @@ -0,0 +1,3 @@ +import {createContext} from 'react' + +export default createContext({initialProps: {}}) diff --git a/packages/sui-react-initial-props/src/initialPropsContext.ts b/packages/sui-react-initial-props/src/initialPropsContext.ts deleted file mode 100644 index 83d89f74f..000000000 --- a/packages/sui-react-initial-props/src/initialPropsContext.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from 'react' - -export default createContext({ initialProps: {} }) diff --git a/packages/sui-react-initial-props/src/loadPage.js b/packages/sui-react-initial-props/src/loadPage.js new file mode 100644 index 000000000..64226bdc1 --- /dev/null +++ b/packages/sui-react-initial-props/src/loadPage.js @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import {useContext} from 'react' + +import InitialPropsContext from './initialPropsContext.js' +import withInitialProps from './withInitialProps.js' + +const EMPTY_GET_INITIAL_PROPS = async () => ({}) + +const createUniversalPage = + routeInfo => + async ({default: Page}) => { + // check if the Page page has a getInitialProps, if not put a resolve with an empty object + Page.getInitialProps = typeof Page.getInitialProps === 'function' ? Page.getInitialProps : EMPTY_GET_INITIAL_PROPS + + // CLIENT + if (typeof window !== 'undefined') { + // let withInitialProps HOC handle client getInitialProps logic + return Promise.resolve(withInitialProps(Page)) + } + // SERVER + // Create a component that gets the initialProps from context + // this context has been created on the `ssrWithComponentWithInitialProps` + const ServerPage = props => { + const {initialProps} = useContext(InitialPropsContext) + return + } + // recover the displayName from the original page + ServerPage.displayName = Page.displayName + // detect if the page has getInitialProps and wrap it with the routeInfo + // if we don't have any getInitialProps, just use a empty function returning an empty object + ServerPage.getInitialProps = (context, req, res) => Page.getInitialProps({context, routeInfo, req, res}) + // return the component to be used on the server + return ServerPage + } + +// TODO: Remove this method on next major as it's using unnecessary contextFactory param +// and unnecesary calling done method instead relying on promises +export default (_, importPage) => async (routeInfo, done) => { + importPage() + .then(createUniversalPage(routeInfo)) + .then(Page => { + done(null, Page) + }) +} diff --git a/packages/sui-react-initial-props/src/loadPage.tsx b/packages/sui-react-initial-props/src/loadPage.tsx deleted file mode 100644 index 74599fc99..000000000 --- a/packages/sui-react-initial-props/src/loadPage.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ -import { useContext } from 'react' - -import InitialPropsContext from './initialPropsContext' -import { ClientPageComponent, DoneImportingPageCallback, ReactRouterTypes, WithInitialPropsComponent } from './types' -import withInitialProps from './withInitialProps' - -const EMPTY_GET_INITIAL_PROPS = async (): Promise => ({}) - -const createUniversalPage = (routeInfo: ReactRouterTypes.RouteInfo) => async ({ default: Page }: {default: ClientPageComponent}) => { - // check if the Page page has a getInitialProps, if not put a resolve with an empty object - Page.getInitialProps = - typeof Page.getInitialProps === 'function' - ? Page.getInitialProps - : EMPTY_GET_INITIAL_PROPS - - // CLIENT - if (typeof window !== 'undefined') { - // let withInitialProps HOC handle client getInitialProps logic - return await Promise.resolve(withInitialProps(Page)) - } - // SERVER - // Create a component that gets the initialProps from context - // this context has been created on the `ssrWithComponentWithInitialProps` - const ServerPage: WithInitialPropsComponent = (props: object) => { - const { initialProps } = useContext(InitialPropsContext) - return - } - // recover the displayName from the original page - ServerPage.displayName = Page.displayName - // detect if the page has getInitialProps and wrap it with the routeInfo - // if we don't have any getInitialProps, just use a empty function returning an empty object - ServerPage.getInitialProps = - async (context: object, req: IncomingMessage.ServerRequest, res: IncomingMessage.ClientResponse) => - await Page.getInitialProps({ context, routeInfo, req, res }) - // return the component to be used on the server - return ServerPage -} - -// TODO: Remove this method on next major as it's using unnecessary contextFactory param -// and unnecesary calling done method instead relying on promises -export default (_: any, importPage: () => Promise) => - async (routeInfo: ReactRouterTypes.RouteInfo, done: DoneImportingPageCallback) => { - importPage() - .then(createUniversalPage(routeInfo)) - .then(Page => { - done(null, Page) - }) - } diff --git a/packages/sui-react-initial-props/src/ssrComponentWithInitialProps.tsx b/packages/sui-react-initial-props/src/ssrComponentWithInitialProps.js similarity index 63% rename from packages/sui-react-initial-props/src/ssrComponentWithInitialProps.tsx rename to packages/sui-react-initial-props/src/ssrComponentWithInitialProps.js index f0ba75dac..63e045ff8 100644 --- a/packages/sui-react-initial-props/src/ssrComponentWithInitialProps.tsx +++ b/packages/sui-react-initial-props/src/ssrComponentWithInitialProps.js @@ -1,26 +1,24 @@ -import { renderToNodeStream, renderToString } from 'react-dom/server' +import {renderToNodeStream, renderToString} from 'react-dom/server' -import InitialPropsContext from './initialPropsContext' -import { InitialProps, SsrComponentWithInitialPropsParams } from './types' +import InitialPropsContext from './initialPropsContext.js' -const hrTimeToMs = (diff: [number, number]): number => diff[0] * 1e3 + diff[1] * 1e-6 +const hrTimeToMs = diff => diff[0] * 1e3 + diff[1] * 1e-6 -export default async function ssrComponentWithInitialProps ({ +export default async function ssrComponentWithInitialProps({ Target, context, req, res, renderProps, useStream = false -}: SsrComponentWithInitialPropsParams): Promise { +}) { const startGetInitialProps = process.hrtime() // use the getInitialProps from the page to retrieve the props to initialize - const { getInitialProps } = - renderProps.components[renderProps.components.length - 1] + const {getInitialProps} = renderProps.components[renderProps.components.length - 1] - const initialProps: InitialProps = await getInitialProps(context, req, res) + const initialProps = await getInitialProps(context, req, res) const diffGetInitialProps = process.hrtime(startGetInitialProps) - const { __HTTP__: http } = initialProps + const {__HTTP__: http} = initialProps if (http?.redirectTo !== undefined) { return { @@ -34,7 +32,7 @@ export default async function ssrComponentWithInitialProps ({ // Create App with Context with the initialProps const AppWithContext = ( - + ) @@ -45,7 +43,7 @@ export default async function ssrComponentWithInitialProps ({ // start to calculate renderToString const startRenderToString = process.hrtime() // render with the needed action - const renderResponse = { [renderResponseKey]: renderAction(AppWithContext) } + const renderResponse = {[renderResponseKey]: renderAction(AppWithContext)} // calculate the difference of time used rendering const diffRenderToString = process.hrtime(startRenderToString) // return all the info diff --git a/packages/sui-react-initial-props/src/withInitialProps.tsx b/packages/sui-react-initial-props/src/withInitialProps.js similarity index 57% rename from packages/sui-react-initial-props/src/withInitialProps.tsx rename to packages/sui-react-initial-props/src/withInitialProps.js index d4a5f4849..0cbbf9563 100644 --- a/packages/sui-react-initial-props/src/withInitialProps.tsx +++ b/packages/sui-react-initial-props/src/withInitialProps.js @@ -1,20 +1,13 @@ -import { useContext, useEffect, useRef, useState } from 'react' +import {useContext, useEffect, useRef, useState} from 'react' import SUIContext from '@s-ui/react-context' -import { RouteInfo } from '@s-ui/react-router/src/types' - -import { - ClientPageComponent, - InitialProps, - WithInitialPropsComponent -} from './types' const INITIAL_PROPS_KEY = '__INITIAL_PROPS__' // used to store the last definition of ClientPage component so it can be reused -let latestClientPage: WithInitialPropsComponent +let latestClientPage -const getInitialPropsFromWindow = (): object | undefined => { +const getInitialPropsFromWindow = () => { // if no window initial props, then do nothing if (typeof window[INITIAL_PROPS_KEY] === 'undefined') return // return retrieved props from window @@ -22,7 +15,7 @@ const getInitialPropsFromWindow = (): object | undefined => { } // extract needed info from props for routeInfo object -const createRouteInfoFromProps = ({ location, params, routes }: RouteInfo): RouteInfo => ({ +const createRouteInfoFromProps = ({location, params, routes}) => ({ location, params, routes @@ -38,26 +31,25 @@ const createRouteInfoFromProps = ({ location, params, routes }: RouteInfo): Rout // and the very same component is matched by the router, so PageComponent just // gets props updated. Also, since PageComponent keeps mounted it will receive // an `isLoading` prop while getInitialProps is in progress. -export default (Page: ClientPageComponent): WithInitialPropsComponent => { - const { keepMounted = false } = Page +export default Page => { // gather window initial props for this Page, if present const windowInitialProps = getInitialPropsFromWindow() // remove the variable of the window window[INITIAL_PROPS_KEY] = null // define Page wrapper component - const ClientPage: WithInitialPropsComponent = (props: RouteInfo & object) => { + const ClientPage = props => { const initialPropsFromWindowRef = useRef(windowInitialProps) // used to know if initialProps has been requested at least once const requestedInitialPropsOnceRef = useRef(windowInitialProps != null) // create routeInfo object from current props which are updated const routeInfo = createRouteInfoFromProps(props) // consume sui context from the context provider - const suiContext: object = useContext(SUIContext) + const suiContext = useContext(SUIContext) // pathName from context is outdated, so we update it from routeInfo - const context = { ...suiContext, pathName: routeInfo.location.pathname } + const context = {...suiContext, pathName: routeInfo.location.pathname} - const [{ initialProps, isLoading }, setState] = useState(() => ({ + const [{initialProps, isLoading}, setState] = useState(() => ({ initialProps: initialPropsFromWindowRef.current ?? {}, isLoading: initialPropsFromWindowRef.current == null })) @@ -65,50 +57,43 @@ export default (Page: ClientPageComponent): WithInitialPropsComponent => { useEffect(() => { // check if got initial props from window, because then there's no need // to request them again from client + if (initialPropsFromWindowRef.current != null) { initialPropsFromWindowRef.current = undefined } else { - // only update state if already request initial props - if (requestedInitialPropsOnceRef.current) { - setState({ initialProps, isLoading: true }) + if (!routeInfo.location.state?.shallow) { + // only update state if already request initial props + if (requestedInitialPropsOnceRef.current) { + setState({initialProps, isLoading: true}) + } + + Page.getInitialProps({context, routeInfo}) + .then(initialProps => { + const {__HTTP__: http} = initialProps + + if (http?.redirectTo !== undefined) { + window.location = http.redirectTo + return + } + + setState({initialProps, isLoading: false}) + }) + .catch(error => { + setState({initialProps: {error}, isLoading: false}) + }) + .finally(() => { + if (requestedInitialPropsOnceRef.current) return + requestedInitialPropsOnceRef.current = true + }) } - - Page.getInitialProps({ context, routeInfo }) - .then((initialProps: InitialProps) => { - const { __HTTP__: http } = initialProps - - if (http?.redirectTo !== undefined) { - window.location = http.redirectTo - return - } - - setState({ initialProps, isLoading: false }) - }) - .catch((error: Error) => { - setState({ initialProps: { error }, isLoading: false }) - }) - .finally(() => { - if (requestedInitialPropsOnceRef.current) return - requestedInitialPropsOnceRef.current = true - }) } }, [routeInfo.location]) // eslint-disable-line react-hooks/exhaustive-deps - const renderPage = (): any => ( - - ) - - // if the page has a `keepMounted` property and already requested - // initialProps once, just keep rendering the page - if (keepMounted && requestedInitialPropsOnceRef.current) { - return renderPage() - } + const renderPage = () => - const renderLoading = (): React.ElementType | null => { + const renderLoading = () => { // check if the page has a `renderLoading` method, if not, just render nothing - return (Page.renderLoading != null) - ? Page.renderLoading({ context, routeInfo }) - : null + return Page.renderLoading != null ? Page.renderLoading({context, routeInfo}) : null } return isLoading ? renderLoading() : renderPage() @@ -116,10 +101,7 @@ export default (Page: ClientPageComponent): WithInitialPropsComponent => { // if `keepMounted` property is found and the component is the same one, // we just reuse it instead of returning a new one - if ( - keepMounted && - Page.displayName === latestClientPage?.Page?.displayName - ) { + if (Page.displayName === latestClientPage?.Page?.displayName) { return latestClientPage }