@@ -20,12 +20,15 @@ declare global {
2020 ReactOnRails : ReactOnRailsType ;
2121 __REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ ?: boolean ;
2222 roots : Root [ ] ;
23+ REACT_ON_RAILS_PENDING_COMPONENT_DOM_IDS ?: string [ ] ;
24+ REACT_ON_RAILS_UNMOUNTED_BEFORE ?: boolean ;
2325 }
2426
2527 namespace NodeJS {
2628 interface Global {
2729 ReactOnRails : ReactOnRailsType ;
2830 roots : Root [ ] ;
31+ REACT_ON_RAILS_PENDING_COMPONENT_DOM_IDS ?: string [ ] ;
2932 }
3033 }
3134 namespace Turbolinks {
@@ -134,7 +137,7 @@ function domNodeIdForEl(el: Element): string {
134137 * Used for client rendering by ReactOnRails. Either calls ReactDOM.hydrate, ReactDOM.render, or
135138 * delegates to a renderer registered by the user.
136139 */
137- function render ( el : Element , context : Context , railsContext : RailsContext ) : void {
140+ async function render ( el : Element , context : Context , railsContext : RailsContext ) : Promise < void > {
138141 // This must match lib/react_on_rails/helper.rb
139142 const name = el . getAttribute ( 'data-component-name' ) || '' ;
140143 const domNodeId = domNodeIdForEl ( el ) ;
@@ -144,7 +147,7 @@ function render(el: Element, context: Context, railsContext: RailsContext): void
144147 try {
145148 const domNode = document . getElementById ( domNodeId ) ;
146149 if ( domNode ) {
147- const componentObj = context . ReactOnRails . getComponent ( name ) ;
150+ const componentObj = await context . ReactOnRails . getOrWaitForComponent ( name ) ;
148151 if ( delegateToRenderer ( componentObj , props , railsContext , domNodeId , trace ) ) {
149152 return ;
150153 }
@@ -180,13 +183,6 @@ You should return a React.Component always for the client side entry point.`);
180183 }
181184}
182185
183- function forEachReactOnRailsComponentRender ( context : Context , railsContext : RailsContext ) : void {
184- const els = reactOnRailsHtmlElements ( ) ;
185- for ( let i = 0 ; i < els . length ; i += 1 ) {
186- render ( els [ i ] , context , railsContext ) ;
187- }
188- }
189-
190186function parseRailsContext ( ) : RailsContext | null {
191187 const el = document . getElementById ( 'js-react-on-rails-context' ) ;
192188 if ( ! el ) {
@@ -202,39 +198,62 @@ function parseRailsContext(): RailsContext | null {
202198 return JSON . parse ( el . textContent ) ;
203199}
204200
201+ function getContextAndRailsContext ( ) : { context : Context ; railsContext : RailsContext | null } {
202+ const railsContext = parseRailsContext ( ) ;
203+ const context = findContext ( ) ;
204+
205+ if ( railsContext && supportsRootApi && ! context . roots ) {
206+ context . roots = [ ] ;
207+ }
208+
209+ return { context, railsContext } ;
210+ }
211+
205212export function reactOnRailsPageLoaded ( ) : void {
206213 debugTurbolinks ( 'reactOnRailsPageLoaded' ) ;
207214
208- const railsContext = parseRailsContext ( ) ;
209-
215+ const { context , railsContext } = getContextAndRailsContext ( ) ;
216+
210217 // If no react on rails components
211218 if ( ! railsContext ) return ;
212219
213- const context = findContext ( ) ;
214- if ( supportsRootApi ) {
215- context . roots = [ ] ;
216- }
217220 forEachStore ( context , railsContext ) ;
218- forEachReactOnRailsComponentRender ( context , railsContext ) ;
219221}
220222
221- export function reactOnRailsComponentLoaded ( domId : string ) : void {
222- debugTurbolinks ( `reactOnRailsComponentLoaded ${ domId } ` ) ;
223+ async function renderUsingDomId ( domId : string , context : Context , railsContext : RailsContext ) {
224+ const el = document . querySelector ( `[data-dom-id=${ domId } ]` ) ;
225+ if ( ! el ) return ;
223226
224- const railsContext = parseRailsContext ( ) ;
227+ await render ( el , context , railsContext ) ;
228+ }
225229
230+ export async function renderOrHydrateLoadedComponents ( ) : Promise < void > {
231+ debugTurbolinks ( 'renderOrHydrateLoadedComponents' ) ;
232+
233+ const { context, railsContext } = getContextAndRailsContext ( ) ;
234+
226235 // If no react on rails components
227236 if ( ! railsContext ) return ;
228237
229- const context = findContext ( ) ;
230- if ( supportsRootApi ) {
231- context . roots = [ ] ;
232- }
238+ // copy and clear the pending dom ids, so they don't get processed again
239+ const pendingDomIds = context . REACT_ON_RAILS_PENDING_COMPONENT_DOM_IDS ?? [ ] ;
240+ context . REACT_ON_RAILS_PENDING_COMPONENT_DOM_IDS = [ ] ;
241+ await Promise . all (
242+ pendingDomIds . map ( async ( domId ) => {
243+ await renderUsingDomId ( domId , context , railsContext ) ;
244+ } )
245+ ) ;
246+ }
233247
234- const el = document . querySelector ( `[data-dom-id=${ domId } ]` ) ;
235- if ( ! el ) return ;
248+ export async function reactOnRailsComponentLoaded ( domId : string ) : Promise < void > {
249+ debugTurbolinks ( `reactOnRailsComponentLoaded ${ domId } ` ) ;
250+
251+ const { context, railsContext } = getContextAndRailsContext ( ) ;
252+
253+ // If no react on rails components
254+ if ( ! railsContext ) return ;
236255
237- render ( el , context , railsContext ) ;
256+ await renderUsingDomId ( domId , context , railsContext ) ;
238257}
239258
240259function unmount ( el : Element ) : void {
@@ -333,5 +352,6 @@ export function clientStartup(context: Context): void {
333352 // eslint-disable-next-line no-underscore-dangle, no-param-reassign
334353 context . __REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ = true ;
335354
355+ console . log ( 'clientStartup' ) ;
336356 onPageReady ( renderInit ) ;
337357}
0 commit comments