Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook to get the server context #737

Merged
merged 12 commits into from
Sep 30, 2022
33 changes: 17 additions & 16 deletions packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router} from 'react-router-dom'
import {CorrelationIdProvider} from '../universal/contexts'
import DeviceContext from '../universal/device-context'
import {DeviceContext, ServerContext, CorrelationIdProvider} from '../universal/contexts'
import App from '../universal/components/_app'
import {getAppConfig} from '../universal/compatibility'
import Switch from '../universal/components/switch'
Expand Down Expand Up @@ -75,20 +74,22 @@ export const start = () => {
.then(() => new Promise((resolve) => loadableReady(resolve)))
.then(() => {
ReactDOM.hydrate(
<Router>
<CorrelationIdProvider correlationId={() => uuidv4()}>
<DeviceContext.Provider value={{type: window.__DEVICE_TYPE__}}>
<AppConfig locals={locals}>
<Switch
error={error}
appState={window.__PRELOADED_STATE__}
routes={routes}
App={WrappedApp}
/>
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>,
<ServerContext.Provider value={{}}>
<Router>
<CorrelationIdProvider correlationId={() => uuidv4()}>
<DeviceContext.Provider value={{type: window.__DEVICE_TYPE__}}>
<AppConfig locals={locals}>
<Switch
error={error}
appState={window.__PRELOADED_STATE__}
routes={routes}
App={WrappedApp}
/>
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>
</ServerContext.Provider>,
rootEl,
() => {
window.__HYDRATING__ = false
Expand Down
39 changes: 27 additions & 12 deletions packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import {StaticRouter as Router, matchPath} from 'react-router-dom'
import serialize from 'serialize-javascript'

import {getAssetUrl} from '../universal/utils'
import DeviceContext from '../universal/device-context'
import {CorrelationIdProvider} from '../universal/contexts'
import {DeviceContext, ServerContext, CorrelationIdProvider} from '../universal/contexts'

import Document from '../universal/components/_document'
import App from '../universal/components/_app'
Expand Down Expand Up @@ -192,22 +191,38 @@ export const render = async (req, res, next) => {
}
}

const OuterApp = ({res, error, App, appState, routes, routerContext, location, deviceType}) => {
const OuterApp = ({
req,
res,
error,
App,
appState,
routes,
routerContext,
location,
deviceType
}) => {
const AppConfig = getAppConfig()
return (
<Router location={location} context={routerContext}>
<CorrelationIdProvider correlationId={res.locals.requestId} resetOnPageChange={false}>
<DeviceContext.Provider value={{type: deviceType}}>
<AppConfig locals={res.locals}>
<Switch error={error} appState={appState} routes={routes} App={App} />
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>
<ServerContext.Provider value={{req, res}}>
<Router location={location} context={routerContext}>
<CorrelationIdProvider
correlationId={res.locals.requestId}
resetOnPageChange={false}
>
<DeviceContext.Provider value={{type: deviceType}}>
<AppConfig locals={res.locals}>
<Switch error={error} appState={appState} routes={routes} App={App} />
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>
</ServerContext.Provider>
)
}

OuterApp.propTypes = {
req: PropTypes.object,
res: PropTypes.object,
error: PropTypes.object,
App: PropTypes.elementType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ export const withReactQuery = (Wrapped) => {
const queryClient = (res.locals.__queryClient =
res.locals.__queryClient || new QueryClient())

await ssrPrepass(appJSX)
// Without the request object, our useServerContext hook would be able tell whether on prepass
const withoutReq = React.cloneElement(appJSX, {
req: undefined
})
Comment on lines +70 to +72
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bendvc I couldn't set both req and res to be undefined. Why only setting req? Because of a few reasons:

  • Our code is relying on the existence of res.locals. For example, see line 25 above.
  • I did try a fake res with res: {locals: {}}. But that has a negative consequence: on the 2nd pass (for line 25), locals.__queryClient would have been undefined... it should have been defined already from the prepass.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I guess this is another good example of why not to use locals. This isn't the ideal solution, but it works.

await ssrPrepass(withoutReq)

const queryCache = queryClient.getQueryCache()
const queries = queryCache.getAll().filter((q) => q.options.enabled !== false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import React, {useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import {useLocation} from 'react-router-dom'

const DeviceContext = React.createContext()
const ServerContext = React.createContext()

const CorrelationIdContext = React.createContext()

/**
Expand Down Expand Up @@ -60,4 +63,4 @@ CorrelationIdProvider.propTypes = {
location: PropTypes.object
}

export {CorrelationIdContext, CorrelationIdProvider}
export {CorrelationIdContext, CorrelationIdProvider, DeviceContext, ServerContext}

This file was deleted.

27 changes: 26 additions & 1 deletion packages/pwa-kit-react-sdk/src/ssr/universal/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/* istanbul ignore file */

import React, {useContext} from 'react'
import {CorrelationIdContext} from '../contexts'
import {CorrelationIdContext, ServerContext} from '../contexts'

/**
* Use this hook to get the correlation id value of the closest CorrelationIdProvider component.
Expand All @@ -21,3 +21,28 @@ export const useCorrelationId = () => {
}
return context
}

/**
* Server context
* @typedef {Object} ServerContext
* @property {Object} req - Request object
* @property {Object} res - Response object
* @property {boolean} isServerSide
*/

/**
* Get the server context
* @returns {ServerContext} ServerContext object
*
* @example
* const {res, isServerSide} = useServerContext()
* if (isServerSide && query.error) { res.status(404) }
*/
export const useServerContext = () => {
const serverContext = useContext(ServerContext)

return {
...serverContext,
isServerSide: Boolean(serverContext.req)
}
}
8 changes: 8 additions & 0 deletions packages/template-typescript-minimal/app/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {useQuery} from '@tanstack/react-query'
import HelloTS from '../components/hello-typescript'
import HelloJS from '../components/hello-javascript'

import {useServerContext} from 'pwa-kit-react-sdk/ssr/universal/hooks'

interface Props {
value: number
}
Expand Down Expand Up @@ -101,6 +103,12 @@ const Home = ({value}: Props) => {
})
)

const {res, isServerSide} = useServerContext()
if (isServerSide && query.data) {
console.log('--- useServerContext')
res.status(404)
}
bendvc marked this conversation as resolved.
Show resolved Hide resolved

return (
<div>
<style dangerouslySetInnerHTML={{__html: style}} />
Expand Down