feat: handle server side props on the client

This commit is contained in:
Valerio Ageno
2024-05-23 20:41:05 +02:00
parent 9a0bb45a03
commit ec2fb17f7f
6 changed files with 94 additions and 16 deletions
@@ -3,7 +3,12 @@ import { useRouterStore } from '../hooks/useRouterStore'
import { RouteMatch } from './RouteMatch'
import NotFound from './NotFound'
export function Matches(): JSX.Element {
interface MatchesProps {
// user defined props
serverSideProps: any
}
export function Matches({ serverSideProps }: MatchesProps): JSX.Element {
const location = useRouterStore((st) => st.location)
const router = useRouter()
@@ -13,5 +18,5 @@ export function Matches(): JSX.Element {
return <NotFound />
}
return <RouteMatch route={route} />
return <RouteMatch route={route} serverSideProps={serverSideProps} />
}
@@ -1,23 +1,28 @@
import type { Route } from '../route'
import { useServerSideProps } from '../hooks/useServerSideProps'
interface MatchProps {
route: Route
// User defined server side props
serverSideProps: any
}
/**
* Returns the route match with the root element if exists
*
* It handles the fetch of the client side resources
*/
export const RouteMatch = ({ route }: MatchProps): JSX.Element => {
if (route.options.hasHandler) {
console.log('Has rust handler')
}
export const RouteMatch = ({
route,
serverSideProps,
}: MatchProps): JSX.Element => {
const { data, isLoading } = useServerSideProps(route, serverSideProps)
if (!route.isRoot) {
console.log(route.options)
return route.options.getParentRoute().component({
children: route.options.component(),
children: route.options.component({ data, isLoading }),
})
}
return route.options.component()
return route.options.component({ data, isLoading })
}
@@ -3,18 +3,18 @@ import type { Router } from '../router'
const routerContext = React.createContext<Router>(null!)
const ROUTER_DOM_CONTEXT = '__TUONO_ROUTER__CONTEXT__'
const TUONO_CONTEXT = '__TUONO_CONTEXT__'
export function getRouterContext(): any {
if (typeof document === 'undefined') {
return routerContext
}
if (window[ROUTER_DOM_CONTEXT as any]) {
return window[ROUTER_DOM_CONTEXT as any]
if (window[TUONO_CONTEXT as any]) {
return window[TUONO_CONTEXT as any]
}
window[ROUTER_DOM_CONTEXT as any] = routerContext as any
window[TUONO_CONTEXT as any] = routerContext as any
return routerContext
}
@@ -45,6 +45,7 @@ interface RouterProviderProps {
interface ServerProps {
router: Location
props: any
}
const initRouterStore = (props?: ServerProps): void => {
@@ -74,13 +75,12 @@ const initRouterStore = (props?: ServerProps): void => {
export function RouterProvider({
router,
serverProps,
...rest
}: RouterProviderProps): JSX.Element {
initRouterStore(serverProps)
return (
<RouterContextProvider router={router} {...rest}>
<Matches />
<RouterContextProvider router={router}>
<Matches serverSideProps={serverProps?.props} />
</RouterContextProvider>
)
}
@@ -0,0 +1,63 @@
import { useState, useEffect, useRef } from 'react'
import type { Route } from '../route'
const isServer = typeof document === 'undefined'
interface UseServerSidePropsReturn {
data: any
isLoading: boolean
}
/*
* Use the props provided by the SSR and dehydrate the
* props for client side usage.
*
* In case is a client fetch the remote data API
*/
export function useServerSideProps(
route: Route,
serverSideProps: any,
): UseServerSidePropsReturn {
const isFirstRendering = useRef<boolean>(true)
const [isLoading, setIsLoading] = useState<boolean>(
// Force loading if has handler
route.options.hasHandler &&
// Avoid loading on the server
!isServer &&
// Avoid loading if first rendering
!isFirstRendering.current,
)
const [data, setData] = useState(
serverSideProps || window.__TUONO_SSR_PROPS__.props,
)
useEffect(() => {
// First loading just dehydrate since the
// props are already bundled by the SSR
if (isFirstRendering.current) {
isFirstRendering.current = false
return
}
// After client side routing load again the remote data
if (route.options.hasHandler && !data) {
;(async (): Promise<void> => {
setIsLoading(true)
try {
const res = await fetch(`/__tuono/data/${route.path}`)
setData(await res.json())
} catch (error) {
// Handle here error
} finally {
setIsLoading(false)
}
})()
}
// Clean up the data when changing route
return (): void => {
setData(undefined)
}
}, [route.path])
return { isLoading, data }
}
+5
View File
@@ -1,3 +1,8 @@
declare global {
interface Window {
__TUONO_SSR__PROPS__: any
}
}
export { RouterProvider } from './components/RouterProvider'
export { default as Link } from './components/Link'
export { createRouter } from './router'