Skip to content

Commit 46f92a6

Browse files
add support for returning react component from async render function
1 parent 0c8faa0 commit 46f92a6

File tree

3 files changed

+45
-29
lines changed

3 files changed

+45
-29
lines changed

node_package/src/createReactOutput.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@ import * as React from 'react';
22
import type { CreateParams, ReactComponent, RenderFunction, CreateReactOutputResult } from './types/index';
33
import { isServerRenderHash, isPromise } from './isServerRenderResult';
44

5+
function createReactElementFromRenderFunctionResult(
6+
renderFunctionResult: ReactComponent,
7+
name: string,
8+
props: Record<string, unknown> | undefined,
9+
): React.ReactElement {
10+
if (React.isValidElement(renderFunctionResult)) {
11+
// If already a ReactElement, then just return it.
12+
console.error(
13+
`Warning: ReactOnRails: Your registered render-function (ReactOnRails.register) for ${name}
14+
incorrectly returned a React Element (JSX). Instead, return a React Function Component by
15+
wrapping your JSX in a function. ReactOnRails v13 will throw error on this, as React Hooks do not
16+
work if you return JSX. Update by wrapping the result JSX of ${name} in a fat arrow function.`,
17+
);
18+
return renderFunctionResult;
19+
}
20+
21+
// If a component, then wrap in an element
22+
return React.createElement(renderFunctionResult, props);
23+
}
24+
525
/**
626
* Logic to either call the renderFunction or call React.createElement to get the
727
* React.Component
@@ -60,24 +80,15 @@ export default function createReactOutput({
6080
if (typeof result === 'string') {
6181
return result;
6282
}
83+
// If the result is a function, then it returned a React Component (even class components are functions).
84+
if (typeof result === 'function') {
85+
return createReactElementFromRenderFunctionResult(result, name, props);
86+
}
6387
return JSON.stringify(result);
6488
});
6589
}
6690

67-
if (React.isValidElement(renderFunctionResult)) {
68-
// If already a ReactElement, then just return it.
69-
console.error(
70-
`Warning: ReactOnRails: Your registered render-function (ReactOnRails.register) for ${name}
71-
incorrectly returned a React Element (JSX). Instead, return a React Function Component by
72-
wrapping your JSX in a function. ReactOnRails v13 will throw error on this, as React Hooks do not
73-
work if you return JSX. Update by wrapping the result JSX of ${name} in a fat arrow function.`,
74-
);
75-
return renderFunctionResult;
76-
}
77-
78-
// If a component, then wrap in an element
79-
const reactComponent = renderFunctionResult;
80-
return React.createElement(reactComponent, props);
91+
return createReactElementFromRenderFunctionResult(renderFunctionResult, name, props);
8192
}
8293
// else
8394
return React.createElement(component as ReactComponent, props);

node_package/src/serverRenderReactComponent.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,19 @@ function processServerRenderHash(result: ServerRenderResult, options: RenderOpti
4444
return { result: htmlResult, hasErrors };
4545
}
4646

47+
function processReactElement(result: ReactElement): string {
48+
try {
49+
return ReactDOMServer.renderToString(result);
50+
} catch (error) {
51+
console.error(`Invalid call to renderToString. Possibly you have a renderFunction, a function that already
52+
calls renderToString, that takes one parameter. You need to add an extra unused parameter to identify this function
53+
as a renderFunction and not a simple React Function Component.`);
54+
throw error;
55+
}
56+
}
57+
4758
function processPromise(
48-
result: Promise<string>,
59+
result: Promise<string | ReactElement>,
4960
renderingReturnsPromises: boolean,
5061
): Promise<string> | string {
5162
if (!renderingReturnsPromises) {
@@ -56,18 +67,12 @@ function processPromise(
5667
// And when a promise is passed to JSON.stringify, it will be converted to '{}'.
5768
return '{}';
5869
}
59-
return result;
60-
}
61-
62-
function processReactElement(result: ReactElement): string {
63-
try {
64-
return ReactDOMServer.renderToString(result);
65-
} catch (error) {
66-
console.error(`Invalid call to renderToString. Possibly you have a renderFunction, a function that already
67-
calls renderToString, that takes one parameter. You need to add an extra unused parameter to identify this function
68-
as a renderFunction and not a simple React Function Component.`);
69-
throw error;
70-
}
70+
return result.then((promiseResult) => {
71+
if (typeof promiseResult === 'string') {
72+
return promiseResult;
73+
}
74+
return processReactElement(promiseResult);
75+
});
7176
}
7277

7378
function processRenderingResult(result: CreateReactOutputResult, options: RenderOptions): RenderState {

node_package/src/types/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ interface ServerRenderResult {
5454
error?: Error;
5555
}
5656

57-
type CreateReactOutputResult = ServerRenderResult | ReactElement | Promise<string>;
57+
type CreateReactOutputResult = ServerRenderResult | ReactElement | Promise<string | ReactElement>;
5858

5959
type RenderFunctionResult =
6060
| ReactComponent
6161
| ServerRenderResult
62-
| Promise<string | ServerRenderHashRenderedHtml>;
62+
| Promise<string | ServerRenderHashRenderedHtml | ReactComponent>;
6363

6464
/**
6565
* Render functions are used to create dynamic React components or server-rendered HTML with side effects.

0 commit comments

Comments
 (0)