Skip to content

Commit

Permalink
Merge pull request #15 from rambler-digital-solutions/hydration
Browse files Browse the repository at this point in the history
fix: preload server route before hydration
  • Loading branch information
andrepolischuk authored Dec 13, 2023
2 parents 09e6bb8 + 2be8055 commit ba64dc9
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 14 deletions.
4 changes: 2 additions & 2 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[
{
"path": "dist/client/index.js",
"limit": "7.01 KB"
"limit": "7.1 KB"
},
{
"path": "dist/server/index.js",
"limit": "7.02 KB"
"limit": "7.1 KB"
}
]
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,9 @@ hydrateFromStream({

By default, router uses `blocked` transition mode, and will wait for `getInitialData` to get completed to show the next page.

If your want show the next page with spinner or skeleton while `getInitialData` is pending, use `instant` transition mode.
If your want show the next page with spinner or skeleton while `getInitialData` is pending, use `wait-for-data` transition mode.

And if your want show the next page with spinner or skeleton only while lazy page is pending, use `instant` transition mode.

```ts
// src/routes.ts
Expand Down
14 changes: 13 additions & 1 deletion src/client/stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {BrowserRouter} from 'react-router-dom'
import {RenderOptions, TransitionMode} from '../common/types'
import {getState} from '../components/state'
import {AppContextProvider} from '../components/context'
import {matchRoute} from '../components/loader'
import {Routes} from '../components/routes'
import {Layout as BaseLayout} from '../components/layout'
import {Document as BaseDocument} from '../components/document'
Expand Down Expand Up @@ -33,7 +34,8 @@ export interface HydrateFromStreamOptions extends RenderOptions {
export const hydrateFromStream = async (
options: HydrateFromStreamOptions
): Promise<void> => {
const state = getState()
const {pathname, ...state} = getState()

const {
routes,
Layout = BaseLayout,
Expand Down Expand Up @@ -64,5 +66,15 @@ export const hydrateFromStream = async (
</AppContextProvider>
)

const match = matchRoute({pathname, routes})

if (match) {
const {
route: {Component}
} = match

await Component.preload?.()
}

hydrateRoot(document, app)
}
1 change: 1 addition & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export type PageComponent<P = any> = React.ComponentType<
/** Lazy page component */
export interface LazyPageComponent<P = any>
extends React.LazyExoticComponent<PageComponent<P>> {
preload: Loader<void>
getMetaData: GetMetaData
getInitialData: GetInitialData
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/lazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ export const lazy = (componentFactory: ComponentFactory): LazyPageComponent => {
}

function loaderFactory<T, C = any>(
dataFactory: DataFactory<T, C>
dataFactory?: DataFactory<T, C>
): Loader<T, C> {
return async (context: Context & C) => {
const {default: Component} = await onceFactory()

return dataFactory(Component, context) ?? Promise.resolve()
return dataFactory?.(Component, context)
}
}

Expand All @@ -60,5 +60,7 @@ export const lazy = (componentFactory: ComponentFactory): LazyPageComponent => {
({getInitialData}, context) => getInitialData?.(context)
)

Component.preload = loaderFactory<void>()

return Component
}
22 changes: 18 additions & 4 deletions src/components/loader.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import {matchRoutes, type RouteMatch} from 'react-router-dom'
import {Context, PageRoute, InitialData, MetaData} from '../common/types'

/** Route loader options */
export interface LoadRouteDataOptions {
context: Context
/** Match route options */
export interface MatchRouteOptions {
pathname: string
routes: PageRoute[]
}

/** Match route */
export const matchRoute = ({
pathname,
routes
}: MatchRouteOptions): RouteMatch<string, any> => {
const [match] = matchRoutes<any>(routes, pathname) ?? []

return match
}

/** Route loader options */
export interface LoadRouteDataOptions extends MatchRouteOptions {
context: Context
}

/** Route data */
export interface RouteData {
data?: InitialData
Expand All @@ -21,7 +35,7 @@ export const loadRouteData = async ({
routes,
context
}: LoadRouteDataOptions): Promise<RouteData> => {
const [match] = matchRoutes<any>(routes, pathname) ?? []
const match = matchRoute({pathname, routes})

if (!match) {
return {
Expand Down
2 changes: 1 addition & 1 deletion src/components/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const Routes: React.FC<RoutesProps> = ({
key={path}
path={path}
element={
<Suspense fallback={Fallback ? <Fallback /> : null}>
<Suspense fallback={Fallback ? <Fallback /> : undefined}>
{isWaitingMode && routeData.isLoading && Fallback ? (
<Fallback />
) : (
Expand Down
6 changes: 4 additions & 2 deletions src/components/state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface StateProps {
* ```
*/
export const State: React.FC<StateProps> = ({name = STATE_NAME, state}) => {
const {data, meta, styles, scripts} = useAppContext()
const {data, meta, styles, scripts, req} = useAppContext()

if (!isSSR) {
return
Expand All @@ -49,7 +49,9 @@ export const State: React.FC<StateProps> = ({name = STATE_NAME, state}) => {
id={`__${name.toUpperCase()}__`}
type="application/json"
dangerouslySetInnerHTML={{
__html: stringify(state ?? {data, meta, styles, scripts})
__html: stringify(
state ?? {data, meta, styles, scripts, pathname: req?.path}
)
}}
/>
)
Expand Down
1 change: 0 additions & 1 deletion src/server/stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export const renderToStream = async (
pathname,
search
} as Location,

...rest
}

Expand Down

0 comments on commit ba64dc9

Please sign in to comment.