Skip to content

Commit 40c52de

Browse files
authored
[Flight] Add Runtime Errors for Non-serializable Values (#19980)
* Error on encoding non-serializable props * Add DEV time warnings to enforce that values are plain objects
1 parent 6eca8ef commit 40c52de

File tree

3 files changed

+368
-6
lines changed

3 files changed

+368
-6
lines changed

packages/react-client/src/__tests__/ReactFlight-test.js

+121
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let ReactNoop;
1818
let ReactNoopFlightServer;
1919
let ReactNoopFlightServerRuntime;
2020
let ReactNoopFlightClient;
21+
let ErrorBoundary;
2122

2223
describe('ReactFlight', () => {
2324
beforeEach(() => {
@@ -29,6 +30,27 @@ describe('ReactFlight', () => {
2930
ReactNoopFlightServerRuntime = require('react-noop-renderer/flight-server-runtime');
3031
ReactNoopFlightClient = require('react-noop-renderer/flight-client');
3132
act = ReactNoop.act;
33+
34+
ErrorBoundary = class extends React.Component {
35+
state = {hasError: false, error: null};
36+
static getDerivedStateFromError(error) {
37+
return {
38+
hasError: true,
39+
error,
40+
};
41+
}
42+
componentDidMount() {
43+
expect(this.state.hasError).toBe(true);
44+
expect(this.state.error).toBeTruthy();
45+
expect(this.state.error.message).toContain(this.props.expectedMessage);
46+
}
47+
render() {
48+
if (this.state.hasError) {
49+
return this.state.error.message;
50+
}
51+
return this.props.children;
52+
}
53+
};
3254
});
3355

3456
function block(render, load) {
@@ -127,4 +149,103 @@ describe('ReactFlight', () => {
127149
expect(ReactNoop).toMatchRenderedOutput(<span>Hello, Seb Smith</span>);
128150
});
129151
}
152+
153+
it('should error if a non-serializable value is passed to a host component', () => {
154+
function EventHandlerProp() {
155+
return (
156+
<div className="foo" onClick={function() {}}>
157+
Test
158+
</div>
159+
);
160+
}
161+
function FunctionProp() {
162+
return <div>{() => {}}</div>;
163+
}
164+
function SymbolProp() {
165+
return <div foo={Symbol('foo')} />;
166+
}
167+
168+
const event = ReactNoopFlightServer.render(<EventHandlerProp />);
169+
const fn = ReactNoopFlightServer.render(<FunctionProp />);
170+
const symbol = ReactNoopFlightServer.render(<SymbolProp />);
171+
172+
function Client({transport}) {
173+
return ReactNoopFlightClient.read(transport);
174+
}
175+
176+
act(() => {
177+
ReactNoop.render(
178+
<>
179+
<ErrorBoundary expectedMessage="Event handlers cannot be passed to client component props.">
180+
<Client transport={event} />
181+
</ErrorBoundary>
182+
<ErrorBoundary expectedMessage="Functions cannot be passed directly to client components because they're not serializable.">
183+
<Client transport={fn} />
184+
</ErrorBoundary>
185+
<ErrorBoundary expectedMessage="Symbol values (foo) cannot be passed to client components.">
186+
<Client transport={symbol} />
187+
</ErrorBoundary>
188+
</>,
189+
);
190+
});
191+
});
192+
193+
it('should warn in DEV if a toJSON instance is passed to a host component', () => {
194+
expect(() => {
195+
const transport = ReactNoopFlightServer.render(
196+
<input value={new Date()} />,
197+
);
198+
act(() => {
199+
ReactNoop.render(ReactNoopFlightClient.read(transport));
200+
});
201+
}).toErrorDev(
202+
'Only plain objects can be passed to client components from server components. ',
203+
{withoutStack: true},
204+
);
205+
});
206+
207+
it('should warn in DEV if a special object is passed to a host component', () => {
208+
expect(() => {
209+
const transport = ReactNoopFlightServer.render(<input value={Math} />);
210+
act(() => {
211+
ReactNoop.render(ReactNoopFlightClient.read(transport));
212+
});
213+
}).toErrorDev(
214+
'Only plain objects can be passed to client components from server components. ' +
215+
'Built-ins like Math are not supported.',
216+
{withoutStack: true},
217+
);
218+
});
219+
220+
it('should warn in DEV if an object with symbols is passed to a host component', () => {
221+
expect(() => {
222+
const transport = ReactNoopFlightServer.render(
223+
<input value={{[Symbol.iterator]: {}}} />,
224+
);
225+
act(() => {
226+
ReactNoop.render(ReactNoopFlightClient.read(transport));
227+
});
228+
}).toErrorDev(
229+
'Only plain objects can be passed to client components from server components. ' +
230+
'Objects with symbol properties like Symbol.iterator are not supported.',
231+
{withoutStack: true},
232+
);
233+
});
234+
235+
it('should warn in DEV if a class instance is passed to a host component', () => {
236+
class Foo {
237+
method() {}
238+
}
239+
expect(() => {
240+
const transport = ReactNoopFlightServer.render(
241+
<input value={new Foo()} />,
242+
);
243+
act(() => {
244+
ReactNoop.render(ReactNoopFlightClient.read(transport));
245+
});
246+
}).toErrorDev(
247+
'Only plain objects can be passed to client components from server components. ',
248+
{withoutStack: true},
249+
);
250+
});
130251
});

0 commit comments

Comments
 (0)