Skip to content

Commit

Permalink
Partial fix for SSR.
Browse files Browse the repository at this point in the history
  • Loading branch information
GirlBossRush committed Oct 19, 2023
1 parent 3ee3989 commit 5da9b4f
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 25 deletions.
12 changes: 8 additions & 4 deletions client/BrowserRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,18 @@ export const BrowserRouter: React.FC<BrowserRouterProps> = ({
}, [currentHistory])

return (
<SSRPropsProvider initialLocation={initialNavigatorURL} initialPropsByPath={initialPropsByPath}>
<FetchEventProvider event={fetchEvent} logger={logger}>
<FetchEventProvider event={fetchEvent} logger={logger}>
<SSRPropsProvider
currentLocation={window.location}
initialLocation={initialNavigatorURL}
initialPropsByPath={initialPropsByPath}
>
<URLContext.Provider value={initialNavigatorURL}>
<BrowserRouterContext.Provider value={value}>
<KeyworkPatternToPageComponent routes={routes} />
</BrowserRouterContext.Provider>
</URLContext.Provider>
</FetchEventProvider>
</SSRPropsProvider>
</SSRPropsProvider>
</FetchEventProvider>
)
}
7 changes: 4 additions & 3 deletions client/KeyworkPatternToPageComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@
*/

import { useMemo } from 'react'
import { useRequest } from '../http/RequestContext.js'
import { PatternRouteComponentMap, RoutePatternsProps, URLPatternResultContext } from '../uri/index.js'
import { useSSRPropsByPath } from './SSRPropsProvider.js'
import { useLocation } from './hooks.js'

/**
* @beta
* @ignore
*/
export const KeyworkPatternToPageComponent: React.FC<RoutePatternsProps> = ({ routes }) => {
const location = useLocation()
const request = useRequest()

const staticPropsByPath = useSSRPropsByPath()
const patternRouteComponentMap = useMemo(() => new PatternRouteComponentMap(routes), [routes])
const result = useMemo(() => patternRouteComponentMap.match(location), [location, patternRouteComponentMap])
const result = useMemo(() => patternRouteComponentMap.match(request.url), [patternRouteComponentMap, request.url])

if (!result) {
return <FallbackComponent />
Expand Down
19 changes: 11 additions & 8 deletions client/SSRPropsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { useKeyworkLogger } from '../logging/index.js'
import { KEYWORK_STATIC_PROPS_QUERY_KEY } from '../uri/index.js'
import { useLocation } from './hooks.js'

/**
* A mapping of static props to a given path.
Expand All @@ -30,21 +29,24 @@ export const useSSRPropsByPath = () => useContext(SSRPropsContext)
export interface SSRPropsProviderProps {
initialPropsByPath: SSRPropsByPath
initialLocation: URL
currentLocation?: Location
children: React.ReactNode
}

export const SSRPropsProvider: React.FC<SSRPropsProviderProps> = ({
initialPropsByPath,
initialLocation,
currentLocation,
children,
}) => {
const lastRenderLocationRef = useRef(initialLocation)
const [propsByPath, setPropsByPath] = useState(initialPropsByPath)
const location = useLocation()
const logger = useKeyworkLogger()

const fetchRouteData = useCallback(async () => {
const url = new URL(location.pathname, (globalThis as any).location.origin)
if (!currentLocation) return

const url = new URL(currentLocation.pathname, (globalThis as any).location.origin)
url.searchParams.set(KEYWORK_STATIC_PROPS_QUERY_KEY, 'true')

const response = await fetch(url)
Expand All @@ -57,15 +59,16 @@ export const SSRPropsProvider: React.FC<SSRPropsProviderProps> = ({

const data = await response.json()

setPropsByPath(new Map([[location.pathname, data]]))
}, [location.pathname, logger])
setPropsByPath(new Map([[currentLocation.pathname, data]]))
}, [currentLocation, logger])

useEffect(() => {
if (lastRenderLocationRef.current.pathname !== location.pathname) {
lastRenderLocationRef.current = new URL(location.pathname, lastRenderLocationRef.current.origin)
if (!currentLocation) return
if (lastRenderLocationRef.current.pathname !== currentLocation.pathname) {
lastRenderLocationRef.current = new URL(currentLocation.pathname, lastRenderLocationRef.current.origin)
fetchRouteData()
}
}, [fetchRouteData, location])
}, [currentLocation, fetchRouteData])

return <SSRPropsContext.Provider value={propsByPath}>{children}</SSRPropsContext.Provider>
}
2 changes: 1 addition & 1 deletion client/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import { useEffect, useState } from 'react'

export function useLocation() {
const [currentLocation, setCurrentLocation] = useState(location)
const [currentLocation, setCurrentLocation] = useState<Location>(location)

useEffect(() => {
const popStateHandler = (_event: PopStateEvent) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "keywork",
"version": "8.1.17",
"version": "8.1.19",
"license": "AGPL-3.0",
"author": "Teffen Ellis <[email protected]>",
"description": "A batteries-included, magic-free, library for building web apps in V8 Isolates.",
Expand Down
8 changes: 4 additions & 4 deletions ssr/RequestDocument.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,16 @@ export const RequestDocument: React.FC<RequestDocumentProps> = ({
const initialPropsByPath: SSRPropsByPath = new Map([[initialNavigatorURL.pathname, staticProps]])

const appDocument = (
<SSRPropsProvider initialLocation={initialNavigatorURL} initialPropsByPath={initialPropsByPath}>
<FetchEventProvider event={event} logger={logger}>
<FetchEventProvider event={event} logger={logger}>
<SSRPropsProvider initialLocation={initialNavigatorURL} initialPropsByPath={initialPropsByPath}>
<Providers>
<DocumentComponent event={event} importMap={importMap}>
{pageElement}
<KeyworkSSREmbed eventInit={event.toJSON()} staticProps={staticProps} />
</DocumentComponent>
</Providers>
</FetchEventProvider>
</SSRPropsProvider>
</SSRPropsProvider>
</FetchEventProvider>
)

return appDocument
Expand Down
4 changes: 2 additions & 2 deletions uri/URLPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ interface NormalizeURLPatternOptions {
export function normalizeURLPatternInput(patternLike: URLPatternLike): URLPatternInput {
if (!patternLike) throw new KeyworkResourceError('`patternLike` must be defined')

if (patternLike instanceof URL) return patternLike

if (isURLPathname(patternLike)) return URLPathnameToURLPatternInput(patternLike)

if (isKeyworkRouteComponent(patternLike)) return normalizeURLPattern(patternLike.pathname!)

if (patternLike instanceof URL) return patternLike

return patternLike
}

Expand Down
7 changes: 5 additions & 2 deletions uri/URLPatternRouteMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import {
IURLPattern,
KeyworkRouteComponent,
URLPathnameInput,
URLPatternLike,
URLPatternResult,
isKeyworkRouteComponent,
normalizeURLPattern,
normalizeURLPatternInput,
} from './URLPattern.js'

export type RoutePatternEntry = [string, KeyworkRouteComponent<any>]
Expand Down Expand Up @@ -81,10 +83,11 @@ export class PatternRouteComponentMap extends Map<string, React.ComponentType<an
return super.get(pathnamePattern)
}

public match(location: URLPathnameInput): LocationPatternResult | null {
public match(input: URLPatternLike): LocationPatternResult | null {
for (const [patternLike, Component] of this.entries()) {
// TODO: Consider submapping of this to avoid recompiling patterns.
const pattern = normalizeURLPattern(patternLike)
const match = pattern.exec({ pathname: location.pathname })
const match = pattern.exec(normalizeURLPatternInput(input))

if (!match) continue

Expand Down

0 comments on commit 5da9b4f

Please sign in to comment.