Skip to content

Commit

Permalink
RSC: Extract the fetchRSC function (redwoodjs#10564)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe committed May 12, 2024
1 parent 2decb56 commit f77331a
Showing 1 changed file with 90 additions and 83 deletions.
173 changes: 90 additions & 83 deletions packages/vite/src/client.ts
Expand Up @@ -20,6 +20,94 @@ const checkStatus = async (

const BASE_PATH = '/rw-rsc/'

type SetRerender = (
rerender: (next: [Thenable<ReactElement>, string]) => void,
) => () => void

function fetchRSC(
rscId: string,
serializedProps: string,
): readonly [Thenable<ReactElement>, SetRerender] {
console.log('fetchRSC serializedProps', serializedProps)

let rerender: ((next: [Thenable<ReactElement>, string]) => void) | undefined

const setRerender: SetRerender = (fn) => {
rerender = fn
return () => {
rerender = undefined
}
}

const searchParams = new URLSearchParams()
searchParams.set('props', serializedProps)

const options: Options<unknown[], ReactElement> = {
// React will hold on to `callServer` and use that when it detects a
// server action is invoked (like `action={onSubmit}` in a <form>
// element). So for now at least we need to send it with every RSC
// request, so React knows what `callServer` method to use for server
// actions inside the RSC.
callServer: async function (rsfId: string, args: unknown[]) {
// `args` is often going to be an array with just a single element,
// and that element will be FormData
console.log('client.ts :: callServer rsfId', rsfId, 'args', args)

const isMutating = !!mutationMode
const searchParams = new URLSearchParams()
searchParams.set('action_id', rsfId)
let id: string

if (isMutating) {
id = rscId
searchParams.set('props', serializedProps)
} else {
id = '_'
}

const response = fetch(BASE_PATH + id + '?' + searchParams, {
method: 'POST',
body: await encodeReply(args),
headers: {
'rw-rsc': '1',
},
})

// I'm not sure this recursive use of `options` is needed. I briefly
// tried without it, and things seemed to work. But keeping it for
// now, until we learn more.
const data = createFromFetch(response, options)

if (isMutating) {
rerender?.([data, serializedProps])
}

return data
},
}

const prefetched = (globalThis as any).__WAKU_PREFETCHED__?.[rscId]?.[
serializedProps
]

console.log(
'fetchRSC before createFromFetch',
BASE_PATH + rscId + '?' + searchParams,
)

const response =
prefetched ||
fetch(BASE_PATH + rscId + '?' + searchParams, {
headers: {
'rw-rsc': '1',
},
})
const data = createFromFetch(checkStatus(response), options)
console.log('fetchRSC after createFromFetch. data:', data)

return [data, setRerender]
}

export function renderFromRscServer<TProps>(rscId: string) {
console.log('serve rscId (renderFromRscServer)', rscId)

Expand All @@ -31,88 +119,7 @@ export function renderFromRscServer<TProps>(rscId: string) {
)
}

type SetRerender = (
rerender: (next: [Thenable<ReactElement>, string]) => void,
) => () => void

const fetchRSC = cache(
(
serializedProps: string,
): readonly [Thenable<ReactElement>, SetRerender] => {
console.log('fetchRSC serializedProps', serializedProps)

let rerender:
| ((next: [Thenable<ReactElement>, string]) => void)
| undefined

const setRerender: SetRerender = (fn) => {
rerender = fn
return () => {
rerender = undefined
}
}

const searchParams = new URLSearchParams()
searchParams.set('props', serializedProps)

const options: Options<unknown[], ReactElement> = {
// `args` is often going to be an array with just a single element,
// and that element will be FormData
callServer: async function (rsfId: string, args: unknown[]) {
console.log('client.ts :: callServer rsfId', rsfId, 'args', args)

const isMutating = !!mutationMode
const searchParams = new URLSearchParams()
searchParams.set('action_id', rsfId)
let id: string

if (isMutating) {
id = rscId
searchParams.set('props', serializedProps)
} else {
id = '_'
}

const response = fetch(BASE_PATH + id + '?' + searchParams, {
method: 'POST',
body: await encodeReply(args),
headers: {
'rw-rsc': '1',
},
})

const data = createFromFetch(response, options)

if (isMutating) {
rerender?.([data, serializedProps])
}

return data
},
}

const prefetched = (globalThis as any).__WAKU_PREFETCHED__?.[rscId]?.[
serializedProps
]

console.log(
'fetchRSC before createFromFetch',
BASE_PATH + rscId + '?' + searchParams,
)

const response =
prefetched ||
fetch(BASE_PATH + rscId + '?' + searchParams, {
headers: {
'rw-rsc': '1',
},
})
const data = createFromFetch(checkStatus(response), options)
console.log('fetchRSC after createFromFetch. data:', data)

return [data, setRerender]
},
)
const cachedFetchRSC = cache(fetchRSC)

// Create temporary client component that wraps the ServerComponent returned
// by the `createFromFetch` call.
Expand All @@ -121,7 +128,7 @@ export function renderFromRscServer<TProps>(rscId: string) {

// FIXME we blindly expect JSON.stringify usage is deterministic
const serializedProps = JSON.stringify(props || {})
const [data, setRerender] = fetchRSC(serializedProps)
const [data, setRerender] = cachedFetchRSC(rscId, serializedProps)
const [state, setState] = useState<
| [dataToOverride: Thenable<ReactElement>, lastSerializedProps: string]
| undefined
Expand Down

0 comments on commit f77331a

Please sign in to comment.