Skip to content

Commit 0c8faa0

Browse files
update TS types to handle the case when render function return object
1 parent 43169ad commit 0c8faa0

File tree

5 files changed

+38
-27
lines changed

5 files changed

+38
-27
lines changed

node_package/src/createReactOutput.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import * as React from 'react';
2-
import type {
3-
ServerRenderResult,
4-
CreateParams,
5-
ReactComponent,
6-
RenderFunction,
7-
CreateReactOutputResult,
8-
} from './types/index';
2+
import type { CreateParams, ReactComponent, RenderFunction, CreateReactOutputResult } from './types/index';
93
import { isServerRenderHash, isPromise } from './isServerRenderResult';
104

115
/**
@@ -53,16 +47,21 @@ export default function createReactOutput({
5347
console.log(`${name} is a renderFunction`);
5448
}
5549
const renderFunctionResult = (component as RenderFunction)(props, railsContext);
56-
if (isServerRenderHash(renderFunctionResult as CreateReactOutputResult)) {
50+
if (isServerRenderHash(renderFunctionResult)) {
5751
// We just return at this point, because calling function knows how to handle this case and
5852
// we can't call React.createElement with this type of Object.
59-
return renderFunctionResult as ServerRenderResult;
53+
return renderFunctionResult;
6054
}
6155

62-
if (isPromise(renderFunctionResult as CreateReactOutputResult)) {
56+
if (isPromise(renderFunctionResult)) {
6357
// We just return at this point, because calling function knows how to handle this case and
6458
// we can't call React.createElement with this type of Object.
65-
return renderFunctionResult as Promise<string>;
59+
return renderFunctionResult.then((result) => {
60+
if (typeof result === 'string') {
61+
return result;
62+
}
63+
return JSON.stringify(result);
64+
});
6665
}
6766

6867
if (React.isValidElement(renderFunctionResult)) {
@@ -77,7 +76,7 @@ work if you return JSX. Update by wrapping the result JSX of ${name} in a fat ar
7776
}
7877

7978
// If a component, then wrap in an element
80-
const reactComponent = renderFunctionResult as ReactComponent;
79+
const reactComponent = renderFunctionResult;
8180
return React.createElement(reactComponent, props);
8281
}
8382
// else
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import type { CreateReactOutputResult, ServerRenderResult } from './types/index';
1+
import type { CreateReactOutputResult, ServerRenderResult, RenderFunctionResult } from './types/index';
22

3-
export function isServerRenderHash(testValue: CreateReactOutputResult): testValue is ServerRenderResult {
3+
export function isServerRenderHash(
4+
testValue: CreateReactOutputResult | RenderFunctionResult,
5+
): testValue is ServerRenderResult {
46
return !!(
57
(testValue as ServerRenderResult).renderedHtml ||
68
(testValue as ServerRenderResult).redirectLocation ||
@@ -10,7 +12,7 @@ export function isServerRenderHash(testValue: CreateReactOutputResult): testValu
1012
}
1113

1214
export function isPromise<T>(
13-
testValue: CreateReactOutputResult | Promise<T> | string | null,
15+
testValue: CreateReactOutputResult | RenderFunctionResult | Promise<T> | string | null,
1416
): testValue is Promise<T> {
1517
return !!(testValue as Promise<T> | null)?.then;
1618
}

node_package/src/serverRenderReactComponent.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ function processServerRenderHash(result: ServerRenderResult, options: RenderOpti
3535
// For redirects on server rendering, we can't stop Rails from returning the same result.
3636
// Possibly, someday, we could have the Rails server redirect.
3737
htmlResult = '';
38+
} else if (typeof result.renderedHtml === 'string') {
39+
htmlResult = result.renderedHtml;
3840
} else {
39-
htmlResult = result.renderedHtml as string;
41+
htmlResult = JSON.stringify(result.renderedHtml);
4042
}
4143

4244
return { result: htmlResult, hasErrors };

node_package/src/types/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,24 @@ type AuthenticityHeaders = Record<string, string> & {
4242

4343
type StoreGenerator = (props: Record<string, unknown>, railsContext: RailsContext) => Store;
4444

45+
type ServerRenderHashRenderedHtml = {
46+
componentHtml: string;
47+
[key: string]: string;
48+
};
49+
4550
interface ServerRenderResult {
46-
renderedHtml?: string | { componentHtml: string; [key: string]: string };
51+
renderedHtml?: string | ServerRenderHashRenderedHtml;
4752
redirectLocation?: { pathname: string; search: string };
4853
routeError?: Error;
4954
error?: Error;
5055
}
5156

5257
type CreateReactOutputResult = ServerRenderResult | ReactElement | Promise<string>;
5358

54-
type RenderFunctionResult = ReactComponent | ServerRenderResult | Promise<string>;
59+
type RenderFunctionResult =
60+
| ReactComponent
61+
| ServerRenderResult
62+
| Promise<string | ServerRenderHashRenderedHtml>;
5563

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

node_package/tests/serverRenderReactComponent.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,15 +183,15 @@ describe('serverRenderReactComponent', () => {
183183
await expect(renderResult.then((r) => r.hasErrors)).resolves.toBeFalsy();
184184
});
185185

186-
// When an async render function returns an object, serverRenderReactComponent will return the object as is.
187-
// It does not validate properties like renderedHtml or hasErrors; it simply returns the object.
186+
// When an async render function returns an object, serverRenderReactComponent will return the object as it after stringifying it.
187+
// It does not validate properties like renderedHtml or hasErrors; it simply returns the stringified object.
188188
// This behavior can cause issues with the ruby_on_rails gem.
189189
// To avoid such issues, ensure that the returned object includes a `componentHtml` property and use the `react_component_hash` helper.
190190
// This is demonstrated in the "can render async render function used with react_component_hash helper" test.
191191
it('serverRenderReactComponent returns the object returned by the async render function', async () => {
192-
const expectedHtml = '<div>Hello</div>';
192+
const resultObject = { renderedHtml: '<div>Hello</div>' };
193193
const X6 = ((_props: unknown, _railsContext?: RailsContext): Promise<ServerRenderResult> =>
194-
Promise.resolve({ renderedHtml: expectedHtml })) as RenderFunction;
194+
Promise.resolve(resultObject)) as RenderFunction;
195195

196196
ComponentRegistry.register({ X6 });
197197

@@ -205,16 +205,16 @@ describe('serverRenderReactComponent', () => {
205205

206206
const renderResult = serverRenderReactComponent(renderParams);
207207
assertIsPromise(renderResult);
208-
const html = (await renderResult.then((r) => r.html)) as ServerRenderResult;
209-
expectMatchObject(html, { renderedHtml: expectedHtml });
208+
const html = await renderResult.then((r) => r.html);
209+
expect(html).toEqual(JSON.stringify(resultObject));
210210
});
211211

212212
// Because the object returned by the async render function is returned as it is,
213213
// we can make the async render function returns an object with `componentHtml` property.
214214
// This is useful when we want to render a component using the `react_component_hash` helper.
215215
it('can render async render function used with react_component_hash helper', async () => {
216-
const X7 = (_props: unknown, _railsContext?: RailsContext) =>
217-
Promise.resolve({ componentHtml: '<div>Hello</div>' });
216+
const reactComponentHashResult = { componentHtml: '<div>Hello</div>' };
217+
const X7 = (_props: unknown, _railsContext?: RailsContext) => Promise.resolve(reactComponentHashResult);
218218

219219
ComponentRegistry.register({ X7: X7 as unknown as RenderFunction });
220220

@@ -229,7 +229,7 @@ describe('serverRenderReactComponent', () => {
229229
const renderResult = serverRenderReactComponent(renderParams);
230230
assertIsPromise(renderResult);
231231
const result = await renderResult;
232-
expect(result.html).toMatchObject({ componentHtml: '<div>Hello</div>' });
232+
expect(result.html).toEqual(JSON.stringify(reactComponentHashResult));
233233
});
234234

235235
it('serverRenderReactComponent renders an error if attempting to render a renderer', () => {

0 commit comments

Comments
 (0)