@@ -91,6 +91,16 @@ def react_component(component_name, options = {})
9191 end
9292 end
9393
94+ def stream_react_component ( component_name , options = { } )
95+ options = options . merge ( stream? : true )
96+ result = internal_react_component ( component_name , options )
97+ build_react_component_result_for_server_streamed_content (
98+ rendered_html_stream : result [ :result ] ,
99+ component_specification_tag : result [ :tag ] ,
100+ render_options : result [ :render_options ]
101+ )
102+ end
103+
94104 # react_component_hash is used to return multiple HTML strings for server rendering, such as for
95105 # adding meta-tags to a page.
96106 # It is exactly like react_component except for the following:
@@ -334,6 +344,10 @@ def generated_components_pack_path(react_component_name)
334344 "#{ ReactOnRails ::WebpackerUtils . webpacker_source_entry_path } /generated/#{ react_component_name } .js"
335345 end
336346
347+ def get_content_tag_options_html_tag ( render_options )
348+
349+ end
350+
337351 def build_react_component_result_for_server_rendered_string (
338352 server_rendered_html : required ( "server_rendered_html" ) ,
339353 component_specification_tag : required ( "component_specification_tag" ) ,
@@ -361,6 +375,33 @@ def build_react_component_result_for_server_rendered_string(
361375 prepend_render_rails_context ( result )
362376 end
363377
378+ def build_react_component_result_for_server_streamed_content (
379+ rendered_html_stream : required ( "rendered_html_stream" ) ,
380+ component_specification_tag : required ( "component_specification_tag" ) ,
381+ render_options : required ( "render_options" )
382+ )
383+ content_tag_options_html_tag = render_options . html_options [ :tag ] || 'div'
384+ # The component_specification_tag is appended to the first chunk
385+ # We need to pass it early with the first chunk because it's needed in hydration
386+ # We need to make sure that client can hydrate the app early even before all components are streamed
387+ is_first_chunk = true
388+
389+ rendered_html_stream = rendered_html_stream . prepend { rails_context_if_not_already_rendered }
390+ . prepend { "<#{ content_tag_options_html_tag } id=\" #{ render_options . dom_id } \" >" }
391+ . transform ( &:html_safe )
392+
393+ rendered_html_stream = rendered_html_stream . transform do |chunk |
394+ is_first_chunk = false
395+ if is_first_chunk
396+ return "#{ chunk } \n #{ component_specification_tag } "
397+ end
398+ chunk
399+ end
400+ . append { "</#{ content_tag_options_html_tag } >" }
401+ . append { component_specification_tag }
402+ # TODO: handle console logs
403+ end
404+
364405 def build_react_component_result_for_server_rendered_hash (
365406 server_rendered_html : required ( "server_rendered_html" ) ,
366407 component_specification_tag : required ( "component_specification_tag" ) ,
@@ -404,20 +445,22 @@ def compose_react_component_html_with_spec_and_console(component_specification_t
404445 HTML
405446 end
406447
407- # prepend the rails_context if not yet applied
408- def prepend_render_rails_context ( render_value )
409- return render_value if @rendered_rails_context
448+ def rails_context_if_not_already_rendered
449+ return "" if @rendered_rails_context
410450
411451 data = rails_context ( server_side : false )
412452
413453 @rendered_rails_context = true
414454
415- rails_context_content = content_tag ( :script ,
416- json_safe_and_pretty ( data ) . html_safe ,
417- type : "application/json" ,
418- id : "js-react-on-rails-context" )
455+ content_tag ( :script ,
456+ json_safe_and_pretty ( data ) . html_safe ,
457+ type : "application/json" ,
458+ id : "js-react-on-rails-context" )
459+ end
419460
420- "#{ rails_context_content } \n #{ render_value } " . html_safe
461+ # prepend the rails_context if not yet applied
462+ def prepend_render_rails_context ( render_value )
463+ "#{ rails_context_if_not_already_rendered } \n #{ render_value } " . html_safe
421464 end
422465
423466 def internal_react_component ( react_component_name , options = { } )
@@ -512,6 +555,9 @@ def server_rendered_react_component(render_options)
512555 js_code : js_code )
513556 end
514557
558+ # TODO: handle errors for streams
559+ return result if render_options . stream?
560+
515561 if result [ "hasErrors" ] && render_options . raise_on_prerender_error
516562 # We caught this exception on our backtrace handler
517563 raise ReactOnRails ::PrerenderError . new ( component_name : react_component_name ,
0 commit comments