11import { raw } from '../helper/html'
22import type { HtmlEscapedCallback , HtmlEscapedString } from '../utils/html'
33import { HtmlEscapedCallbackPhase , resolveCallback } from '../utils/html'
4+ import { jsx , Fragment } from './base'
45import { DOM_RENDERER } from './constants'
56import { useContext } from './context'
67import { ErrorBoundary as ErrorBoundaryDomRenderer } from './dom/components'
@@ -25,6 +26,21 @@ export const childrenToString = async (children: Child[]): Promise<HtmlEscapedSt
2526 }
2627}
2728
29+ const resolveChildEarly = ( c : Child ) : HtmlEscapedString | Promise < HtmlEscapedString > => {
30+ if ( c == null || typeof c === 'boolean' ) {
31+ return '' as HtmlEscapedString
32+ } else if ( typeof c === 'string' ) {
33+ return c as HtmlEscapedString
34+ } else {
35+ const str = c . toString ( )
36+ if ( ! ( str instanceof Promise ) ) {
37+ return raw ( str )
38+ } else {
39+ return str as Promise < HtmlEscapedString >
40+ }
41+ }
42+ }
43+
2844export type ErrorHandler = ( error : Error ) => void
2945export type FallbackRender = ( error : Error ) => Child
3046
@@ -51,37 +67,51 @@ export const ErrorBoundary: FC<
5167 const nonce = useContext ( StreamingContext ) ?. scriptNonce
5268
5369 let fallbackStr : string | undefined
54- const fallbackRes = ( error : Error ) : HtmlEscapedString => {
70+ const resolveFallbackStr = async ( ) => {
71+ const awaitedFallback = await fallback
72+ if ( typeof awaitedFallback === 'string' ) {
73+ fallbackStr = awaitedFallback
74+ } else {
75+ fallbackStr = await awaitedFallback ?. toString ( )
76+ if ( typeof fallbackStr === 'string' ) {
77+ // should not apply `raw` if fallbackStr is undefined, null, or boolean
78+ fallbackStr = raw ( fallbackStr )
79+ }
80+ }
81+ }
82+ const fallbackRes = ( error : Error ) : HtmlEscapedString | Promise < HtmlEscapedString > => {
5583 onError ?.( error )
56- return ( fallbackStr || fallbackRender ?.( error ) || '' ) . toString ( ) as HtmlEscapedString
84+ return ( fallbackStr ||
85+ ( fallbackRender && jsx ( Fragment , { } , fallbackRender ( error ) as HtmlEscapedString ) ) ||
86+ '' ) as HtmlEscapedString
5787 }
5888 let resArray : HtmlEscapedString [ ] | Promise < HtmlEscapedString [ ] > [ ] = [ ]
5989 try {
60- resArray = children . map ( ( c ) =>
61- c == null || typeof c === 'boolean' ? '' : c . toString ( )
62- ) as HtmlEscapedString [ ]
90+ resArray = children . map ( resolveChildEarly ) as unknown as HtmlEscapedString [ ]
6391 } catch ( e ) {
64- fallbackStr = await fallback ?. toString ( )
92+ await resolveFallbackStr ( )
6593 if ( e instanceof Promise ) {
6694 resArray = [
6795 e . then ( ( ) => childrenToString ( children as Child [ ] ) ) . catch ( ( e ) => fallbackRes ( e ) ) ,
6896 ] as Promise < HtmlEscapedString [ ] > [ ]
6997 } else {
70- resArray = [ fallbackRes ( e as Error ) ]
98+ resArray = [ fallbackRes ( e as Error ) as HtmlEscapedString ]
7199 }
72100 }
73101
74102 if ( resArray . some ( ( res ) => ( res as { } ) instanceof Promise ) ) {
75- fallbackStr ||= await fallback ?. toString ( )
103+ await resolveFallbackStr ( )
76104 const index = errorBoundaryCounter ++
77105 const replaceRe = RegExp ( `(<template id="E:${ index } "></template>.*?)(.*?)(<!--E:${ index } -->)` )
78106 const caught = false
79- const catchCallback = ( { error, buffer } : { error : Error ; buffer ?: [ string ] } ) => {
107+ const catchCallback = async ( { error, buffer } : { error : Error ; buffer ?: [ string ] } ) => {
80108 if ( caught ) {
81109 return ''
82110 }
83111
84- const fallbackResString = fallbackRes ( error )
112+ const fallbackResString = await Fragment ( {
113+ children : fallbackRes ( error ) ,
114+ } ) . toString ( )
85115 if ( buffer ) {
86116 buffer [ 0 ] = buffer [ 0 ] . replace ( replaceRe , fallbackResString )
87117 }
@@ -195,7 +225,7 @@ d.remove()
195225 } ,
196226 ] )
197227 } else {
198- return raw ( resArray . join ( '' ) )
228+ return Fragment ( { children : resArray as Child [ ] } )
199229 }
200230}
201231; ( ErrorBoundary as HasRenderToDom ) [ DOM_RENDERER ] = ErrorBoundaryDomRenderer
0 commit comments