mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
feat: handle server side props on the client
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user