@samitouri / QOS-React-1 / commits / 2e84e16299

[Flight] Better error message if you pass a function as a child to a client component (#28367)

Similar to #28362 but if you pass it to a client component.

Sebastian Markbåge committed Feb 19, 2024 at 12:36 UTC 2e84e1629924e6cb278638305fa92040f6ef6eb5
5 files changed +57 -18
packages/react-client/src/__tests__/ReactFlight-test.js
+24 -7
@@ -689,7 +689,7 @@ describe('ReactFlight', () => {
689 );
690 }
691 function FunctionProp() {
692 - return <div>{() => {}}</div>;
692 + return <div>{function fn() {}}</div>;
693 }
694 function SymbolProp() {
695 return <div foo={Symbol('foo')} />;
@@ -707,8 +707,11 @@ describe('ReactFlight', () => {
707 </Client>
708 );
709 }
710 + function FunctionChildrenClient() {
711 + return <Client>{function Component() {}}</Client>;
712 + }
713 function FunctionPropClient() {
711 - return <Client>{() => {}}</Client>;
714 + return <Client foo={() => {}} />;
715 }
716 function SymbolPropClient() {
717 return <Client foo={Symbol('foo')} />;
@@ -731,6 +734,10 @@ describe('ReactFlight', () => {
734 <EventHandlerPropClient />,
735 options,
736 );
737 + const fnChildrenClient = ReactNoopFlightServer.render(
738 + <FunctionChildrenClient />,
739 + options,
740 + );
741 const fnClient = ReactNoopFlightServer.render(
742 <FunctionPropClient />,
743 options,
@@ -754,7 +761,9 @@ describe('ReactFlight', () => {
761 </ErrorBoundary>
762 <ErrorBoundary
763 expectedMessage={
757 - 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".'
764 + __DEV__
765 + ? 'Functions are not valid as a child of Client Components. This may happen if you return fn instead of <fn /> from render. Or maybe you meant to call this function rather than return it.'
766 + : 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".'
767 }>
768 <Render promise={ReactNoopFlightClient.read(fn)} />
769 </ErrorBoundary>
@@ -767,6 +776,14 @@ describe('ReactFlight', () => {
776 <ErrorBoundary expectedMessage="Event handlers cannot be passed to Client Component props.">
777 <Render promise={ReactNoopFlightClient.read(eventClient)} />
778 </ErrorBoundary>
779 + <ErrorBoundary
780 + expectedMessage={
781 + __DEV__
782 + ? 'Functions are not valid as a child of Client Components. This may happen if you return Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.'
783 + : 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".'
784 + }>
785 + <Render promise={ReactNoopFlightClient.read(fnChildrenClient)} />
786 + </ErrorBoundary>
787 <ErrorBoundary
788 expectedMessage={
789 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".'
@@ -945,8 +962,8 @@ describe('ReactFlight', () => {
962 'Only plain objects can be passed to Client Components from Server Components. ' +
963 'Objects with toJSON methods are not supported. ' +
964 'Convert it manually to a simple value before passing it to props.\n' +
948 - ' <input value={{toJSON: function}}>\n' +
949 - ' ^^^^^^^^^^^^^^^^^^^^',
965 + ' <input value={{toJSON: ...}}>\n' +
966 + ' ^^^^^^^^^^^^^^^',
967 {withoutStack: true},
968 );
969 });
@@ -1035,8 +1052,8 @@ describe('ReactFlight', () => {
1052 'Only plain objects can be passed to Client Components from Server Components. ' +
1053 'Objects with toJSON methods are not supported. ' +
1054 'Convert it manually to a simple value before passing it to props.\n' +
1038 - ' <>Current date: {{toJSON: function}}</>\n' +
1039 - ' ^^^^^^^^^^^^^^^^^^^^',
1055 + ' <>Current date: {{toJSON: ...}}</>\n' +
1056 + ' ^^^^^^^^^^^^^^^',
1057 {withoutStack: true},
1058 );
1059 });
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+8 -6
@@ -1710,15 +1710,17 @@ describe('ReactFlightDOM', () => {
1710 expect(reportedErrors.length).toBe(1);
1711 if (__DEV__) {
1712 expect(reportedErrors[0].message).toEqual(
1713 - 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".\n' +
1714 - ' <... prop={client} invalid={function}>\n' +
1715 - ' ^^^^^^^^^^',
1713 + 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". ' +
1714 + 'Or maybe you meant to call this function rather than return it.\n' +
1715 + ' <... prop={client} invalid={function InvalidValue}>\n' +
1716 + ' ^^^^^^^^^^^^^^^^^^^^^^^',
1717 );
1718 } else {
1719 expect(reportedErrors[0].message).toEqual(
1719 - 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".\n' +
1720 - ' {prop: client, invalid: function}\n' +
1721 - ' ^^^^^^^^',
1720 + 'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". ' +
1721 + 'Or maybe you meant to call this function rather than return it.\n' +
1722 + ' {prop: client, invalid: function InvalidValue}\n' +
1723 + ' ^^^^^^^^^^^^^^^^^^^^^',
1724 );
1725 }
1726 });
packages/react-server/src/ReactFlightServer.js
+18 -1
@@ -1554,10 +1554,27 @@ function renderModelDestructive(
1554 describeObjectForErrorMessage(parent, parentPropertyName) +
1555 '\nIf you need interactivity, consider converting part of this to a Client Component.',
1556 );
1557 + } else if (
1558 + __DEV__ &&
1559 + (jsxChildrenParents.has(parent) ||
1560 + (jsxPropsParents.has(parent) && parentPropertyName === 'children'))
1561 + ) {
1562 + const componentName = value.displayName || value.name || 'Component';
1563 + throw new Error(
1564 + 'Functions are not valid as a child of Client Components. This may happen if ' +
1565 + 'you return ' +
1566 + componentName +
1567 + ' instead of <' +
1568 + componentName +
1569 + ' /> from render. ' +
1570 + 'Or maybe you meant to call this function rather than return it.' +
1571 + describeObjectForErrorMessage(parent, parentPropertyName),
1572 + );
1573 } else {
1574 throw new Error(
1575 'Functions cannot be passed directly to Client Components ' +
1560 - 'unless you explicitly expose it by marking it with "use server".' +
1576 + 'unless you explicitly expose it by marking it with "use server". ' +
1577 + 'Or maybe you meant to call this function rather than return it.' +
1578 describeObjectForErrorMessage(parent, parentPropertyName),
1579 );
1580 }
packages/shared/ReactSerializationErrors.js
+4 -2
@@ -107,11 +107,13 @@ export function describeValueForErrorMessage(value: mixed): string {
107 }
108 return name;
109 }
110 - case 'function':
110 + case 'function': {
111 if ((value: any).$$typeof === CLIENT_REFERENCE_TAG) {
112 return describeClientReference(value);
113 }
114 - return 'function';
114 + const name = (value: any).displayName || value.name;
115 + return name ? 'function ' + name : 'function';
116 + }
117 default:
118 // eslint-disable-next-line react-internal/safe-string-coercion
119 return String(value);
scripts/error-codes/codes.json
+3 -2
@@ -361,7 +361,7 @@
361 "372": "Cannot call unstable_createEventHandle with \"%s\", as it is not an event known to React.",
362 "373": "This Hook is not supported in Server Components.",
363 "374": "Event handlers cannot be passed to Client Component props.%s\nIf you need interactivity, consider converting part of this to a Client Component.",
364 - "375": "Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with \"use server\".%s",
364 + "375": "Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with \"use server\". Or maybe you meant to call this function rather than return it.%s",
365 "376": "Only global symbols received from Symbol.for(...) can be passed to Client Components. The symbol Symbol.for(%s) cannot be found among global symbols.%s",
366 "377": "BigInt (%s) is not yet supported in Client Component props.%s",
367 "378": "Type %s is not supported in Client Component props.%s",
@@ -490,5 +490,6 @@
490 "502": "Cannot read a Client Context from a Server Component.",
491 "503": "Cannot use() an already resolved Client Reference.",
492 "504": "Failed to read a RSC payload created by a development version of React on the server while using a production version on the client. Always use matching versions on the server and the client.",
493 - "505": "Cannot render an Async Component, Promise or React.Lazy inside React.Children. We recommend not iterating over children and just rendering them plain."
493 + "505": "Cannot render an Async Component, Promise or React.Lazy inside React.Children. We recommend not iterating over children and just rendering them plain.",
494 + "506": "Functions are not valid as a child of Client Components. This may happen if you return %s instead of <%s /> from render. Or maybe you meant to call this function rather than return it.%s"
495 }