main
js 180 lines 6.06 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 /*
11 * The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
12 * and Temporal.* types. See https://github.com/facebook/react/pull/22064.
13 *
14 * The functions in this module will throw an easier-to-understand,
15 * easier-to-debug exception with a clear errors message message explaining the
16 * problem. (Instead of a confusing exception thrown inside the implementation
17 * of the `value` object).
18 */
19
20 // $FlowFixMe[incompatible-type] only called in DEV, so void return is not possible.
21 function typeName(value: mixed): string {
22 if (__DEV__) {
23 // toStringTag is needed for namespaced types like Temporal.Instant
24 const hasToStringTag = typeof Symbol === 'function' && Symbol.toStringTag;
25 const type =
26 (hasToStringTag && (value as any)[Symbol.toStringTag]) ||
27 (value as any).constructor.name ||
28 'Object';
29 // $FlowFixMe[incompatible-type]
30 return type;
31 }
32 }
33
34 // $FlowFixMe[incompatible-type] only called in DEV, so void return is not possible.
35 function willCoercionThrow(value: mixed): boolean {
36 if (__DEV__) {
37 try {
38 testStringCoercion(value);
39 return false;
40 } catch (e) {
41 return true;
42 }
43 }
44 }
45
46 /** @noinline */
47 function testStringCoercion(value: mixed) {
48 // If you ended up here by following an exception call stack, here's what's
49 // happened: you supplied an object or symbol value to React (as a prop, key,
50 // DOM attribute, CSS property, string ref, etc.) and when React tried to
51 // coerce it to a string using `'' + value`, an exception was thrown.
52 //
53 // The most common types that will cause this exception are `Symbol` instances
54 // and Temporal objects like `Temporal.Instant`. But any object that has a
55 // `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this
56 // exception. (Library authors do this to prevent users from using built-in
57 // numeric operators like `+` or comparison operators like `>=` because custom
58 // methods are needed to perform accurate arithmetic or comparison.)
59 //
60 // To fix the problem, coerce this object or symbol value to a string before
61 // passing it to React. The most reliable way is usually `String(value)`.
62 //
63 // To find which value is throwing, check the browser or debugger console.
64 // Before this exception was thrown, there should be `console.error` output
65 // that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the
66 // problem and how that type was used: key, atrribute, input value prop, etc.
67 // In most cases, this console output also shows the component and its
68 // ancestor components where the exception happened.
69 //
70 // eslint-disable-next-line react-internal/safe-string-coercion
71 return '' + (value as any);
72 }
73
74 export function checkAttributeStringCoercion(
75 value: mixed,
76 attributeName: string,
77 ): void | string {
78 if (__DEV__) {
79 if (willCoercionThrow(value)) {
80 console.error(
81 'The provided `%s` attribute is an unsupported type %s.' +
82 ' This value must be coerced to a string before using it here.',
83 attributeName,
84 typeName(value),
85 );
86 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
87 }
88 }
89 }
90
91 export function checkKeyStringCoercion(value: mixed): void | string {
92 if (__DEV__) {
93 if (willCoercionThrow(value)) {
94 console.error(
95 'The provided key is an unsupported type %s.' +
96 ' This value must be coerced to a string before using it here.',
97 typeName(value),
98 );
99 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
100 }
101 }
102 }
103
104 export function checkPropStringCoercion(
105 value: mixed,
106 propName: string,
107 ): void | string {
108 if (__DEV__) {
109 if (willCoercionThrow(value)) {
110 console.error(
111 'The provided `%s` prop is an unsupported type %s.' +
112 ' This value must be coerced to a string before using it here.',
113 propName,
114 typeName(value),
115 );
116 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
117 }
118 }
119 }
120
121 export function checkOptionStringCoercion(
122 value: mixed,
123 propName: string,
124 ): void | string {
125 if (__DEV__) {
126 if (willCoercionThrow(value)) {
127 console.error(
128 'The provided `%s` option is an unsupported type %s.' +
129 ' This value must be coerced to a string before using it here.',
130 propName,
131 typeName(value),
132 );
133 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
134 }
135 }
136 }
137
138 export function checkCSSPropertyStringCoercion(
139 value: mixed,
140 propName: string,
141 ): void | string {
142 if (__DEV__) {
143 if (willCoercionThrow(value)) {
144 console.error(
145 'The provided `%s` CSS property is an unsupported type %s.' +
146 ' This value must be coerced to a string before using it here.',
147 propName,
148 typeName(value),
149 );
150 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
151 }
152 }
153 }
154
155 export function checkHtmlStringCoercion(value: mixed): void | string {
156 if (__DEV__) {
157 if (willCoercionThrow(value)) {
158 console.error(
159 'The provided HTML markup uses a value of unsupported type %s.' +
160 ' This value must be coerced to a string before using it here.',
161 typeName(value),
162 );
163 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
164 }
165 }
166 }
167
168 export function checkFormFieldValueStringCoercion(value: mixed): void | string {
169 if (__DEV__) {
170 if (willCoercionThrow(value)) {
171 console.error(
172 'Form field values (value, checked, defaultValue, or defaultChecked props)' +
173 ' must be strings, not %s.' +
174 ' This value must be coerced to a string before using it here.',
175 typeName(value),
176 );
177 return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
178 }
179 }
180 }