Skip to content

Commit 4829dcd

Browse files
handle errors happen during streaming components
1 parent bcfddd6 commit 4829dcd

File tree

3 files changed

+80
-22
lines changed

3 files changed

+80
-22
lines changed

lib/react_on_rails/helper.rb

+29-12
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,22 @@ def props_string(props)
526526
props.is_a?(String) ? props : props.to_json
527527
end
528528

529+
def raise_prerender_error(json_result, react_component_name, props, js_code)
530+
raise ReactOnRails::PrerenderError.new(
531+
component_name: react_component_name,
532+
props: sanitized_props_string(props),
533+
err: nil,
534+
js_code: js_code,
535+
console_messages: json_result["consoleReplayScript"]
536+
)
537+
end
538+
539+
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
540+
chunk_json_result["hasErrors"] &&
541+
((render_options.raise_on_prerender_error && !chunk_json_result["isShellReady"]) ||
542+
(render_options.raise_non_shell_server_rendering_errors && chunk_json_result["isShellReady"]))
543+
end
544+
529545
# Returns object with values that are NOT html_safe!
530546
def server_rendered_react_component(render_options)
531547
return { "html" => "", "consoleReplayScript" => "" } unless render_options.prerender
@@ -573,19 +589,20 @@ def server_rendered_react_component(render_options)
573589
js_code: js_code)
574590
end
575591

576-
# TODO: handle errors for streams
577-
return result if render_options.stream?
578-
579-
if result["hasErrors"] && render_options.raise_on_prerender_error
580-
# We caught this exception on our backtrace handler
581-
raise ReactOnRails::PrerenderError.new(component_name: react_component_name,
582-
# Sanitize as this might be browser logged
583-
props: sanitized_props_string(props),
584-
err: nil,
585-
js_code: js_code,
586-
console_messages: result["consoleReplayScript"])
587-
592+
if render_options.stream?
593+
# It doesn't make any transformation, it just listening to the streamed chunks and raise error if it has errors
594+
result.transform do |chunk_json_result|
595+
if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
596+
raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
597+
end
598+
chunk_json_result
599+
end
600+
else
601+
if result["hasErrors"] && render_options.raise_on_prerender_error
602+
raise_prerender_error(result, react_component_name, props, js_code)
603+
end
588604
end
605+
589606
result
590607
end
591608

lib/react_on_rails/react_component/render_options.rb

+11
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ def raise_on_prerender_error
8787
retrieve_configuration_value_for(:raise_on_prerender_error)
8888
end
8989

90+
def raise_non_shell_server_rendering_errors
91+
retrieve_react_on_rails_pro_config_value_for(:raise_non_shell_server_rendering_errors)
92+
end
93+
9094
def logging_on_server
9195
retrieve_configuration_value_for(:logging_on_server)
9296
end
@@ -124,6 +128,13 @@ def retrieve_configuration_value_for(key)
124128
ReactOnRails.configuration.public_send(key)
125129
end
126130
end
131+
132+
def retrieve_react_on_rails_pro_config_value_for(key)
133+
options.fetch(key) do
134+
return nil unless ReactOnRails::Utils.react_on_rails_pro?
135+
ReactOnRailsPro.configuration.public_send(key)
136+
end
137+
end
127138
end
128139
end
129140
end

node_package/src/serverRenderReactComponent.ts

+40-10
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,17 @@ const serverRenderReactComponent: typeof serverRenderReactComponentInternal = (o
169169

170170
const stringToStream = (str: string): Readable => {
171171
const stream = new PassThrough();
172-
stream.push(str);
173-
stream.push(null);
172+
stream.write(str);
173+
stream.end();
174174
return stream;
175175
};
176176

177177
export const streamServerRenderedReactComponent = (options: RenderParams): Readable => {
178178
const { name, domNodeId, trace, props, railsContext, throwJsErrors } = options;
179179

180180
let renderResult: null | Readable = null;
181+
let hasErrors = false;
182+
let isShellReady = false;
181183
let previouslyReplayedConsoleMessages: number = 0;
182184

183185
try {
@@ -200,35 +202,63 @@ See https://github.com/shakacode/react_on_rails#renderer-functions`);
200202
throw new Error('Server rendering of streams is not supported for server render hashes or promises.');
201203
}
202204

203-
const transformStream = new Transform({
205+
const transformStream = new PassThrough({
204206
transform(chunk, _, callback) {
205207
const htmlChunk = chunk.toString();
208+
console.log('htmlChunk', htmlChunk);
206209
const consoleReplayScript = buildConsoleReplay(previouslyReplayedConsoleMessages);
207210
previouslyReplayedConsoleMessages = console.history?.length || 0;
208211

209212
const jsonChunk = JSON.stringify({
210213
html: htmlChunk,
211214
consoleReplayScript,
215+
hasErrors,
216+
isShellReady,
212217
});
213-
218+
214219
this.push(jsonChunk);
215220
callback();
216221
}
217222
});
218223

219-
ReactDOMServer.renderToPipeableStream(reactRenderingResult)
220-
.pipe(transformStream);
224+
const renderingStream = ReactDOMServer.renderToPipeableStream(reactRenderingResult, {
225+
onShellError(error) {
226+
// Can't through error here if throwJsErrors is true because the error will happen inside the stream
227+
// And will not be handled by any catch clause
228+
hasErrors = true;
229+
transformStream.write(handleError({
230+
e: error as any,
231+
name,
232+
serverSide: true,
233+
}));
234+
transformStream.end();
235+
},
236+
onShellReady() {
237+
isShellReady = true;
238+
renderingStream.pipe(transformStream);
239+
},
240+
onError(_) {
241+
// Can't through error here if throwJsErrors is true because the error will happen inside the stream
242+
// And will not be handled by any catch clause
243+
hasErrors = true;
244+
},
245+
});
221246

222247
renderResult = transformStream;
223248
} catch (e: any) {
224249
if (throwJsErrors) {
225250
throw e;
226251
}
227252

228-
renderResult = stringToStream(handleError({
229-
e,
230-
name,
231-
serverSide: true,
253+
renderResult = stringToStream(JSON.stringify({
254+
html: handleError({
255+
e,
256+
name,
257+
serverSide: true,
258+
}),
259+
consoleReplayScript: buildConsoleReplay(previouslyReplayedConsoleMessages),
260+
hasErrors: true,
261+
isShellReady,
232262
}));
233263
}
234264

0 commit comments

Comments
 (0)