Skip to content

Commit 3a26fd1

Browse files
Enhance ReactOnRails options management
1 parent 3f4e89d commit 3a26fd1

File tree

3 files changed

+78
-20
lines changed

3 files changed

+78
-20
lines changed

node_package/src/ReactOnRails.client.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
AuthenticityHeaders,
1616
Store,
1717
StoreGenerator,
18+
ReactOnRailsOptions
1819
} from './types';
1920
import reactHydrateOrRender from './reactHydrateOrRender';
2021

@@ -33,13 +34,14 @@ Check your Webpack configuration. Read more at https://github.com/shakacode/reac
3334
/* eslint-enable @typescript-eslint/no-base-to-string */
3435
}
3536

36-
const DEFAULT_OPTIONS = {
37+
const DEFAULT_OPTIONS: ReactOnRailsOptions = {
3738
traceTurbolinks: false,
3839
turbo: false,
40+
rscPayloadGenerationUrlPath: '/rsc_payload',
3941
};
4042

4143
ctx.ReactOnRails = {
42-
options: {},
44+
options: { ...DEFAULT_OPTIONS },
4345
/**
4446
* Main entry point to using the react-on-rails npm package. This is how Rails will be able to
4547
* find you components for rendering.
@@ -117,25 +119,29 @@ ctx.ReactOnRails = {
117119
* Available Options:
118120
* `traceTurbolinks: true|false Gives you debugging messages on Turbolinks events
119121
* `turbo: true|false Turbo (the follower of Turbolinks) events will be registered, if set to true.
122+
* `rscPayloadGenerationUrlPath: string The path or url of the endpoint that will generate the RSC payload.
120123
*/
121-
setOptions(newOptions: { traceTurbolinks?: boolean; turbo?: boolean }): void {
122-
if (typeof newOptions.traceTurbolinks !== 'undefined') {
123-
this.options.traceTurbolinks = newOptions.traceTurbolinks;
124-
125-
// eslint-disable-next-line no-param-reassign
126-
delete newOptions.traceTurbolinks;
124+
setOptions(newOptions: Partial<ReactOnRailsOptions>): void {
125+
if (!newOptions || typeof newOptions !== 'object') {
126+
throw new Error('Error calling ReactOnRails.setOptions: newOptions must be a plain object.');
127127
}
128128

129-
if (typeof newOptions.turbo !== 'undefined') {
130-
this.options.turbo = newOptions.turbo;
131-
132-
// eslint-disable-next-line no-param-reassign
133-
delete newOptions.turbo;
129+
const validOptionKeys = Object.keys(DEFAULT_OPTIONS);
130+
const providedOptionKeys = Object.keys(newOptions);
131+
132+
const invalidOptions = providedOptionKeys.filter(key => !validOptionKeys.includes(key));
133+
if (invalidOptions.length > 0) {
134+
throw new Error(
135+
`Invalid options passed to ReactOnRails.options: ${JSON.stringify(invalidOptions)}`,
136+
);
134137
}
135138

136-
if (Object.keys(newOptions).length > 0) {
137-
throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`);
138-
}
139+
// Filter out undefined values before merging
140+
const definedOptions = Object.fromEntries(
141+
Object.entries(newOptions).filter(([_, value]) => value !== undefined)
142+
);
143+
144+
this.options = { ...this.options, ...definedOptions };
139145
},
140146

141147
/**
@@ -184,7 +190,7 @@ ctx.ReactOnRails = {
184190
* @param key
185191
* @returns option value
186192
*/
187-
option(key: string): string | number | boolean | undefined {
193+
option(key: keyof ReactOnRailsOptions): string | number | boolean | undefined {
188194
return this.options[key];
189195
},
190196

@@ -339,9 +345,9 @@ ctx.ReactOnRails = {
339345
resetOptions(): void {
340346
this.options = { ...DEFAULT_OPTIONS };
341347
},
342-
};
343348

344-
ctx.ReactOnRails.resetOptions();
349+
isRSCBundle: false,
350+
};
345351

346352
ClientStartup.clientStartup(ctx);
347353

node_package/src/types/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ export interface Root {
169169
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- inherited from React 16/17, can't avoid here
170170
export type RenderReturnType = void | Element | Component | Root;
171171

172+
export interface ReactOnRailsOptions {
173+
traceTurbolinks: boolean;
174+
turbo: boolean;
175+
rscPayloadGenerationUrlPath: string;
176+
}
177+
172178
export interface ReactOnRails {
173179
register(components: Record<string, ReactComponentOrRenderFunction>): void;
174180
/** @deprecated Use registerStoreGenerators instead */

node_package/tests/ReactOnRails.test.jsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,53 @@ describe('ReactOnRails', () => {
5555
it('setOptions method throws error for invalid options', () => {
5656
ReactOnRails.resetOptions();
5757
expect.assertions(1);
58-
expect(() => ReactOnRails.setOptions({ foobar: true })).toThrow(/Invalid option/);
58+
expect(() => ReactOnRails.setOptions({ foobar: true })).toThrow(/Invalid options/);
59+
});
60+
61+
it('setOptions allows setting multiple options at once', () => {
62+
ReactOnRails.resetOptions();
63+
ReactOnRails.setOptions({
64+
traceTurbolinks: true,
65+
rscPayloadGenerationUrlPath: '/custom_rsc'
66+
});
67+
expect(ReactOnRails.option('traceTurbolinks')).toBe(true);
68+
expect(ReactOnRails.option('rscPayloadGenerationUrlPath')).toBe('/custom_rsc');
69+
});
70+
71+
it('setOptions preserves unspecified options when setting specific ones', () => {
72+
ReactOnRails.resetOptions();
73+
74+
ReactOnRails.setOptions({
75+
traceTurbolinks: true,
76+
turbo: true,
77+
rscPayloadGenerationUrlPath: '/custom_rsc'
78+
});
79+
80+
ReactOnRails.setOptions({
81+
rscPayloadGenerationUrlPath: '/different_path'
82+
});
83+
84+
expect(ReactOnRails.option('rscPayloadGenerationUrlPath')).toBe('/different_path');
85+
86+
expect(ReactOnRails.option('traceTurbolinks')).toBe(true);
87+
expect(ReactOnRails.option('turbo')).toBe(true);
88+
});
89+
90+
it('setOptions ignores undefined values', () => {
91+
ReactOnRails.resetOptions();
92+
ReactOnRails.setOptions({
93+
traceTurbolinks: true,
94+
turbo: true,
95+
rscPayloadGenerationUrlPath: '/custom_rsc'
96+
});
97+
98+
ReactOnRails.setOptions({
99+
rscPayloadGenerationUrlPath: undefined
100+
});
101+
102+
expect(ReactOnRails.option('rscPayloadGenerationUrlPath')).toBe('/custom_rsc');
103+
expect(ReactOnRails.option('traceTurbolinks')).toBe(true);
104+
expect(ReactOnRails.option('turbo')).toBe(true);
59105
});
60106

61107
it('registerStore throws if passed a falsey object (null, undefined, etc)', () => {

0 commit comments

Comments
 (0)