@samitouri / QOS-React / commits / 8243f3f063

[bug] Fix component name for Portal and add tests (#32640)

Based off: https://github.com/facebook/react/pull/32499 While looking into `React.lazy` issues for built-ins, I noticed we already error for `lazy` with build-ins, but we don't have any tests for `getComponentNameFromType` using all the built-ins. This may be something we should handle, but for now we should at least have tests. Here's why: while writing tests, I noticed we check `type` instead of `$$typeof` for portals: https://github.com/facebook/react/blob/9cdf8a99edcfd94d7420835ea663edca04237527/packages/react-reconciler/src/ReactPortal.js#L25-L32 This PR adds tests for all the built-ins and fixes the portal bug. [Commit to review](https://github.com/facebook/react/pull/32640/commits/e068c167d48d4df01e79db8f13276bb46d7ab439)

Ricky committed Mar 17, 2025 at 11:23 UTC 8243f3f0631698e819c690710a7f18f767068981
2 files changed +277 -3
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+275 -1
@@ -791,7 +791,7 @@ describe('ReactLazy', () => {
791 );
792 });
793
794 - it('throws with a useful error when wrapping fragment with lazy()', async () => {
794 + it('throws with a useful error when wrapping Fragment with lazy()', async () => {
795 const BadLazy = lazy(() => fakeImport(React.Fragment));
796
797 const root = ReactTestRenderer.create(
@@ -817,6 +817,280 @@ describe('ReactLazy', () => {
817 );
818 });
819
820 + // @gate !fb
821 + it('throws with a useful error when wrapping createPortal with lazy()', async () => {
822 + const ReactDOM = require('react-dom');
823 + const container = document.createElement('div');
824 + const portal = ReactDOM.createPortal(<div />, container);
825 + const BadLazy = lazy(() => fakeImport(portal));
826 +
827 + const root = ReactTestRenderer.create(
828 + <Suspense fallback={<Text text="Loading..." />}>
829 + <BadLazy />
830 + </Suspense>,
831 + {
832 + unstable_isConcurrent: true,
833 + },
834 + );
835 +
836 + await waitForAll(['Loading...']);
837 +
838 + await resolveFakeImport(portal);
839 + root.update(
840 + <Suspense fallback={<Text text="Loading..." />}>
841 + <BadLazy />
842 + </Suspense>,
843 + );
844 + await waitForThrow(
845 + 'Element type is invalid. Received a promise that resolves to: Portal. ' +
846 + 'Lazy element type must resolve to a class or function.',
847 + );
848 + });
849 +
850 + it('throws with a useful error when wrapping Profiler with lazy()', async () => {
851 + const BadLazy = lazy(() => fakeImport(React.Profiler));
852 +
853 + const root = ReactTestRenderer.create(
854 + <Suspense fallback={<Text text="Loading..." />}>
855 + <BadLazy />
856 + </Suspense>,
857 + {
858 + unstable_isConcurrent: true,
859 + },
860 + );
861 +
862 + await waitForAll(['Loading...']);
863 +
864 + await resolveFakeImport(React.Profiler);
865 + root.update(
866 + <Suspense fallback={<Text text="Loading..." />}>
867 + <BadLazy />
868 + </Suspense>,
869 + );
870 + await waitForThrow(
871 + 'Element type is invalid. Received a promise that resolves to: Profiler. ' +
872 + 'Lazy element type must resolve to a class or function.',
873 + );
874 + });
875 +
876 + it('throws with a useful error when wrapping StrictMode with lazy()', async () => {
877 + const BadLazy = lazy(() => fakeImport(React.StrictMode));
878 +
879 + const root = ReactTestRenderer.create(
880 + <Suspense fallback={<Text text="Loading..." />}>
881 + <BadLazy />
882 + </Suspense>,
883 + {
884 + unstable_isConcurrent: true,
885 + },
886 + );
887 +
888 + await waitForAll(['Loading...']);
889 +
890 + await resolveFakeImport(React.StrictMode);
891 + root.update(
892 + <Suspense fallback={<Text text="Loading..." />}>
893 + <BadLazy />
894 + </Suspense>,
895 + );
896 + await waitForThrow(
897 + 'Element type is invalid. Received a promise that resolves to: StrictMode. ' +
898 + 'Lazy element type must resolve to a class or function.',
899 + );
900 + });
901 +
902 + it('throws with a useful error when wrapping Suspense with lazy()', async () => {
903 + const BadLazy = lazy(() => fakeImport(React.Suspense));
904 +
905 + const root = ReactTestRenderer.create(
906 + <Suspense fallback={<Text text="Loading..." />}>
907 + <BadLazy />
908 + </Suspense>,
909 + {
910 + unstable_isConcurrent: true,
911 + },
912 + );
913 +
914 + await waitForAll(['Loading...']);
915 +
916 + await resolveFakeImport(React.Suspense);
917 + root.update(
918 + <Suspense fallback={<Text text="Loading..." />}>
919 + <BadLazy />
920 + </Suspense>,
921 + );
922 + await waitForThrow(
923 + 'Element type is invalid. Received a promise that resolves to: Suspense. ' +
924 + 'Lazy element type must resolve to a class or function.',
925 + );
926 + });
927 +
928 + it('throws with a useful error when wrapping Context with lazy()', async () => {
929 + const Context = React.createContext(null);
930 + const BadLazy = lazy(() => fakeImport(Context));
931 +
932 + const root = ReactTestRenderer.create(
933 + <Suspense fallback={<Text text="Loading..." />}>
934 + <BadLazy />
935 + </Suspense>,
936 + {
937 + unstable_isConcurrent: true,
938 + },
939 + );
940 +
941 + await waitForAll(['Loading...']);
942 +
943 + await resolveFakeImport(Context);
944 + root.update(
945 + <Suspense fallback={<Text text="Loading..." />}>
946 + <BadLazy />
947 + </Suspense>,
948 + );
949 + await waitForThrow(
950 + gate('enableRenderableContext')
951 + ? 'Element type is invalid. Received a promise that resolves to: Context.Provider. ' +
952 + 'Lazy element type must resolve to a class or function.'
953 + : 'Element type is invalid. Received a promise that resolves to: Context.Consumer. ' +
954 + 'Lazy element type must resolve to a class or function.',
955 + );
956 + });
957 +
958 + // @gate enableRenderableContext
959 + it('throws with a useful error when wrapping Context.Consumer with lazy()', async () => {
960 + const Context = React.createContext(null);
961 + const BadLazy = lazy(() => fakeImport(Context.Consumer));
962 +
963 + const root = ReactTestRenderer.create(
964 + <Suspense fallback={<Text text="Loading..." />}>
965 + <BadLazy />
966 + </Suspense>,
967 + {
968 + unstable_isConcurrent: true,
969 + },
970 + );
971 +
972 + await waitForAll(['Loading...']);
973 +
974 + await resolveFakeImport(Context.Consumer);
975 + root.update(
976 + <Suspense fallback={<Text text="Loading..." />}>
977 + <BadLazy />
978 + </Suspense>,
979 + );
980 + await waitForThrow(
981 + 'Element type is invalid. Received a promise that resolves to: Context.Consumer. ' +
982 + 'Lazy element type must resolve to a class or function.',
983 + );
984 + });
985 +
986 + // @gate enableSuspenseList
987 + it('throws with a useful error when wrapping SuspenseList with lazy()', async () => {
988 + const BadLazy = lazy(() => fakeImport(React.unstable_SuspenseList));
989 +
990 + const root = ReactTestRenderer.create(
991 + <Suspense fallback={<Text text="Loading..." />}>
992 + <BadLazy />
993 + </Suspense>,
994 + {
995 + unstable_isConcurrent: true,
996 + },
997 + );
998 +
999 + await waitForAll(['Loading...']);
1000 +
1001 + await resolveFakeImport(React.unstable_SuspenseList);
1002 + root.update(
1003 + <Suspense fallback={<Text text="Loading..." />}>
1004 + <BadLazy />
1005 + </Suspense>,
1006 + );
1007 + await waitForThrow(
1008 + 'Element type is invalid. Received a promise that resolves to: SuspenseList. ' +
1009 + 'Lazy element type must resolve to a class or function.',
1010 + );
1011 + });
1012 +
1013 + // @gate enableViewTransition
1014 + it('throws with a useful error when wrapping ViewTransition with lazy()', async () => {
1015 + const BadLazy = lazy(() => fakeImport(React.unstable_ViewTransition));
1016 +
1017 + const root = ReactTestRenderer.create(
1018 + <Suspense fallback={<Text text="Loading..." />}>
1019 + <BadLazy />
1020 + </Suspense>,
1021 + {
1022 + unstable_isConcurrent: true,
1023 + },
1024 + );
1025 +
1026 + await waitForAll(['Loading...']);
1027 +
1028 + await resolveFakeImport(React.unstable_ViewTransition);
1029 + root.update(
1030 + <Suspense fallback={<Text text="Loading..." />}>
1031 + <BadLazy />
1032 + </Suspense>,
1033 + );
1034 + await waitForThrow(
1035 + 'Element type is invalid. Received a promise that resolves to: ViewTransition. ' +
1036 + 'Lazy element type must resolve to a class or function.',
1037 + );
1038 + });
1039 +
1040 + // @gate enableActivity
1041 + it('throws with a useful error when wrapping Activity with lazy()', async () => {
1042 + const BadLazy = lazy(() => fakeImport(React.unstable_Activity));
1043 +
1044 + const root = ReactTestRenderer.create(
1045 + <Suspense fallback={<Text text="Loading..." />}>
1046 + <BadLazy />
1047 + </Suspense>,
1048 + {
1049 + unstable_isConcurrent: true,
1050 + },
1051 + );
1052 +
1053 + await waitForAll(['Loading...']);
1054 +
1055 + await resolveFakeImport(React.unstable_Activity);
1056 + root.update(
1057 + <Suspense fallback={<Text text="Loading..." />}>
1058 + <BadLazy />
1059 + </Suspense>,
1060 + );
1061 + await waitForThrow(
1062 + 'Element type is invalid. Received a promise that resolves to: Activity. ' +
1063 + 'Lazy element type must resolve to a class or function.',
1064 + );
1065 + });
1066 +
1067 + // @gate enableTransitionTracing
1068 + it('throws with a useful error when wrapping TracingMarker with lazy()', async () => {
1069 + const BadLazy = lazy(() => fakeImport(React.unstable_TracingMarker));
1070 +
1071 + const root = ReactTestRenderer.create(
1072 + <Suspense fallback={<Text text="Loading..." />}>
1073 + <BadLazy />
1074 + </Suspense>,
1075 + {
1076 + unstable_isConcurrent: true,
1077 + },
1078 + );
1079 +
1080 + await waitForAll(['Loading...']);
1081 +
1082 + await resolveFakeImport(React.unstable_TracingMarker);
1083 + root.update(
1084 + <Suspense fallback={<Text text="Loading..." />}>
1085 + <BadLazy />
1086 + </Suspense>,
1087 + );
1088 + await waitForThrow(
1089 + 'Element type is invalid. Received a promise that resolves to: TracingMarker. ' +
1090 + 'Lazy element type must resolve to a class or function.',
1091 + );
1092 + });
1093 +
1094 it('throws with a useful error when wrapping lazy() multiple times', async () => {
1095 const Lazy1 = lazy(() => fakeImport(Text));
1096 const Lazy2 = lazy(() => fakeImport(Lazy1));
packages/shared/getComponentNameFromType.js
+2 -2
@@ -74,8 +74,6 @@ export default function getComponentNameFromType(type: mixed): string | null {
74 switch (type) {
75 case REACT_FRAGMENT_TYPE:
76 return 'Fragment';
77 - case REACT_PORTAL_TYPE:
78 - return 'Portal';
77 case REACT_PROFILER_TYPE:
78 return 'Profiler';
79 case REACT_STRICT_MODE_TYPE:
@@ -106,6 +104,8 @@ export default function getComponentNameFromType(type: mixed): string | null {
104 }
105 }
106 switch (type.$$typeof) {
107 + case REACT_PORTAL_TYPE:
108 + return 'Portal';
109 case REACT_PROVIDER_TYPE:
110 if (enableRenderableContext) {
111 return null;