@@ -124,6 +124,7 @@ def react_component(component_name, options = {})
124124 # @option options [Boolean] :raise_on_prerender_error Set to true to raise exceptions during server-side rendering
125125 # Any other options are passed to the content tag, including the id.
126126 def stream_react_component ( component_name , options = { } )
127+ options = options . merge ( force_load : true ) unless options . key? ( :force_load )
127128 run_stream_inside_fiber do
128129 internal_stream_react_component ( component_name , options )
129130 end
@@ -193,17 +194,18 @@ def react_component_hash(component_name, options = {})
193194 # props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
194195 # Options
195196 # defer: false -- pass as true if you wish to render this below your component.
196- def redux_store ( store_name , props : { } , defer : false )
197+ # force_load: false -- pass as true if you wish to hydrate this store immediately instead of
198+ # waiting for the page to load.
199+ def redux_store ( store_name , props : { } , defer : false , force_load : false )
197200 redux_store_data = { store_name : store_name ,
198- props : props }
201+ props : props ,
202+ force_load : force_load }
199203 if defer
200- @registered_stores_defer_render ||= [ ]
201- @registered_stores_defer_render << redux_store_data
204+ registered_stores_defer_render << redux_store_data
202205 "YOU SHOULD NOT SEE THIS ON YOUR VIEW -- Uses as a code block, like <% redux_store %> " \
203206 "and not <%= redux store %>"
204207 else
205- @registered_stores ||= [ ]
206- @registered_stores << redux_store_data
208+ registered_stores << redux_store_data
207209 result = render_redux_store_data ( redux_store_data )
208210 prepend_render_rails_context ( result )
209211 end
@@ -215,9 +217,9 @@ def redux_store(store_name, props: {}, defer: false)
215217 # client side rendering of this hydration data, which is a hidden div with a matching class
216218 # that contains a data props.
217219 def redux_store_hydration_data
218- return if @ registered_stores_defer_render. blank?
220+ return if registered_stores_defer_render . blank?
219221
220- @ registered_stores_defer_render. reduce ( +"" ) do |accum , redux_store_data |
222+ registered_stores_defer_render . reduce ( +"" ) do |accum , redux_store_data |
221223 accum << render_redux_store_data ( redux_store_data )
222224 end . html_safe
223225 end
@@ -400,6 +402,25 @@ def run_stream_inside_fiber
400402 rendering_fiber . resume
401403 end
402404
405+ def registered_stores
406+ @registered_stores ||= [ ]
407+ end
408+
409+ def registered_stores_defer_render
410+ @registered_stores_defer_render ||= [ ]
411+ end
412+
413+ def registered_stores_including_deferred
414+ registered_stores + registered_stores_defer_render
415+ end
416+
417+ def create_render_options ( react_component_name , options )
418+ # If no store dependencies are passed, default to all registered stores up till now
419+ options [ :store_dependencies ] ||= registered_stores_including_deferred . map { |store | store [ :store_name ] }
420+ ReactOnRails ::ReactComponent ::RenderOptions . new ( react_component_name : react_component_name ,
421+ options : options )
422+ end
423+
403424 def internal_stream_react_component ( component_name , options = { } )
404425 options = options . merge ( stream? : true )
405426 result = internal_react_component ( component_name , options )
@@ -415,7 +436,7 @@ def internal_rsc_react_component(react_component_name, options = {})
415436 render_options = create_render_options ( react_component_name , options )
416437 json_stream = server_rendered_react_component ( render_options )
417438 json_stream . transform do |chunk |
418- chunk [ :html ] . html_safe
439+ " #{ chunk . to_json } \n " . html_safe
419440 end
420441 end
421442
@@ -510,7 +531,8 @@ def build_react_component_result_for_server_rendered_hash(
510531 )
511532 end
512533
513- def compose_react_component_html_with_spec_and_console ( component_specification_tag , rendered_output , console_script )
534+ def compose_react_component_html_with_spec_and_console ( component_specification_tag , rendered_output ,
535+ console_script )
514536 # IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
515537 html_content = <<~HTML
516538 #{ rendered_output }
@@ -546,18 +568,20 @@ def internal_react_component(react_component_name, options = {})
546568 # (re-hydrate the data). This enables react rendered on the client to see that the
547569 # server has already rendered the HTML.
548570
549- render_options = ReactOnRails ::ReactComponent ::RenderOptions . new ( react_component_name : react_component_name ,
550- options : options )
571+ render_options = create_render_options ( react_component_name , options )
551572
552573 # Setup the page_loaded_js, which is the same regardless of prerendering or not!
553574 # The reason is that React is smart about not doing extra work if the server rendering did its job.
554575 component_specification_tag = content_tag ( :script ,
555576 json_safe_and_pretty ( render_options . client_props ) . html_safe ,
556577 type : "application/json" ,
557578 class : "js-react-on-rails-component" ,
579+ id : "js-react-on-rails-component-#{ render_options . dom_id } " ,
558580 "data-component-name" => render_options . react_component_name ,
559581 "data-trace" => ( render_options . trace ? true : nil ) ,
560- "data-dom-id" => render_options . dom_id )
582+ "data-dom-id" => render_options . dom_id ,
583+ "data-store-dependencies" => render_options . store_dependencies . to_json ,
584+ "data-force-load" => ( render_options . force_load ? true : nil ) )
561585
562586 if render_options . force_load
563587 component_specification_tag . concat (
@@ -579,12 +603,22 @@ def internal_react_component(react_component_name, options = {})
579603 end
580604
581605 def render_redux_store_data ( redux_store_data )
582- result = content_tag ( :script ,
583- json_safe_and_pretty ( redux_store_data [ :props ] ) . html_safe ,
584- type : "application/json" ,
585- "data-js-react-on-rails-store" => redux_store_data [ :store_name ] . html_safe )
606+ store_hydration_data = content_tag ( :script ,
607+ json_safe_and_pretty ( redux_store_data [ :props ] ) . html_safe ,
608+ type : "application/json" ,
609+ "data-js-react-on-rails-store" => redux_store_data [ :store_name ] . html_safe ,
610+ "data-force-load" => ( redux_store_data [ :force_load ] ? true : nil ) )
611+
612+ if redux_store_data [ :force_load ]
613+ store_hydration_data . concat (
614+ content_tag ( :script , <<~JS . strip_heredoc . html_safe
615+ ReactOnRails.reactOnRailsStoreLoaded('#{ redux_store_data [ :store_name ] } ');
616+ JS
617+ )
618+ )
619+ end
586620
587- prepend_render_rails_context ( result )
621+ prepend_render_rails_context ( store_hydration_data )
588622 end
589623
590624 def props_string ( props )
@@ -641,7 +675,7 @@ def server_rendered_react_component(render_options) # rubocop:disable Metrics/Cy
641675 js_code = ReactOnRails ::ServerRenderingJsCode . server_rendering_component_js_code (
642676 props_string : props_string ( props ) . gsub ( "\u2028 " , '\u2028' ) . gsub ( "\u2029 " , '\u2029' ) ,
643677 rails_context : rails_context ( server_side : true ) . to_json ,
644- redux_stores : initialize_redux_stores ,
678+ redux_stores : initialize_redux_stores ( render_options ) ,
645679 react_component_name : react_component_name ,
646680 render_options : render_options
647681 )
@@ -657,10 +691,7 @@ def server_rendered_react_component(render_options) # rubocop:disable Metrics/Cy
657691 js_code : js_code )
658692 end
659693
660- # TODO: handle errors for rsc streams
661- return result if render_options . rsc?
662-
663- if render_options . stream?
694+ if render_options . stream? || render_options . rsc?
664695 result . transform do |chunk_json_result |
665696 if should_raise_streaming_prerender_error? ( chunk_json_result , render_options )
666697 raise_prerender_error ( chunk_json_result , react_component_name , props , js_code )
@@ -675,17 +706,20 @@ def server_rendered_react_component(render_options) # rubocop:disable Metrics/Cy
675706 result
676707 end
677708
678- def initialize_redux_stores
709+ def initialize_redux_stores ( render_options )
679710 result = +<<-JS
680711 ReactOnRails.clearHydratedStores();
681712 JS
682713
683- return result unless @registered_stores . present? || @registered_stores_defer_render . present?
714+ store_dependencies = render_options . store_dependencies
715+ return result unless store_dependencies . present?
684716
685717 declarations = +"var reduxProps, store, storeGenerator;\n "
686- all_stores = ( @registered_stores || [ ] ) + ( @registered_stores_defer_render || [ ] )
718+ store_objects = registered_stores_including_deferred . select do |store |
719+ store_dependencies . include? ( store [ :store_name ] )
720+ end
687721
688- result << all_stores . each_with_object ( declarations ) do |redux_store_data , memo |
722+ result << store_objects . each_with_object ( declarations ) do |redux_store_data , memo |
689723 store_name = redux_store_data [ :store_name ]
690724 props = props_string ( redux_store_data [ :props ] )
691725 memo << <<-JS . strip_heredoc
0 commit comments