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-return] 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: any)[Symbol.toStringTag]) ||
    
  27.       (value: any).constructor.name ||
    
  28.       'Object';
    
  29.     // $FlowFixMe[incompatible-return]
    
  30.     return type;
    
  31.   }
    
  32. }
    
  33. 
    
  34. // $FlowFixMe[incompatible-return] 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. function testStringCoercion(value: mixed) {
    
  47.   // If you ended up here by following an exception call stack, here's what's
    
  48.   // happened: you supplied an object or symbol value to React (as a prop, key,
    
  49.   // DOM attribute, CSS property, string ref, etc.) and when React tried to
    
  50.   // coerce it to a string using `'' + value`, an exception was thrown.
    
  51.   //
    
  52.   // The most common types that will cause this exception are `Symbol` instances
    
  53.   // and Temporal objects like `Temporal.Instant`. But any object that has a
    
  54.   // `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this
    
  55.   // exception. (Library authors do this to prevent users from using built-in
    
  56.   // numeric operators like `+` or comparison operators like `>=` because custom
    
  57.   // methods are needed to perform accurate arithmetic or comparison.)
    
  58.   //
    
  59.   // To fix the problem, coerce this object or symbol value to a string before
    
  60.   // passing it to React. The most reliable way is usually `String(value)`.
    
  61.   //
    
  62.   // To find which value is throwing, check the browser or debugger console.
    
  63.   // Before this exception was thrown, there should be `console.error` output
    
  64.   // that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the
    
  65.   // problem and how that type was used: key, atrribute, input value prop, etc.
    
  66.   // In most cases, this console output also shows the component and its
    
  67.   // ancestor components where the exception happened.
    
  68.   //
    
  69.   // eslint-disable-next-line react-internal/safe-string-coercion
    
  70.   return '' + (value: any);
    
  71. }
    
  72. 
    
  73. export function checkAttributeStringCoercion(
    
  74.   value: mixed,
    
  75.   attributeName: string,
    
  76. ): void | string {
    
  77.   if (__DEV__) {
    
  78.     if (willCoercionThrow(value)) {
    
  79.       console.error(
    
  80.         'The provided `%s` attribute is an unsupported type %s.' +
    
  81.           ' This value must be coerced to a string before using it here.',
    
  82.         attributeName,
    
  83.         typeName(value),
    
  84.       );
    
  85.       return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
    
  86.     }
    
  87.   }
    
  88. }
    
  89. 
    
  90. export function checkKeyStringCoercion(value: mixed): void | string {
    
  91.   if (__DEV__) {
    
  92.     if (willCoercionThrow(value)) {
    
  93.       console.error(
    
  94.         'The provided key is an unsupported type %s.' +
    
  95.           ' This value must be coerced to a string before using it here.',
    
  96.         typeName(value),
    
  97.       );
    
  98.       return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
    
  99.     }
    
  100.   }
    
  101. }
    
  102. 
    
  103. export function checkPropStringCoercion(
    
  104.   value: mixed,
    
  105.   propName: string,
    
  106. ): void | string {
    
  107.   if (__DEV__) {
    
  108.     if (willCoercionThrow(value)) {
    
  109.       console.error(
    
  110.         'The provided `%s` prop is an unsupported type %s.' +
    
  111.           ' This value must be coerced to a string before using it here.',
    
  112.         propName,
    
  113.         typeName(value),
    
  114.       );
    
  115.       return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
    
  116.     }
    
  117.   }
    
  118. }
    
  119. 
    
  120. export function checkCSSPropertyStringCoercion(
    
  121.   value: mixed,
    
  122.   propName: string,
    
  123. ): void | string {
    
  124.   if (__DEV__) {
    
  125.     if (willCoercionThrow(value)) {
    
  126.       console.error(
    
  127.         'The provided `%s` CSS property is an unsupported type %s.' +
    
  128.           ' This value must be coerced to a string before using it here.',
    
  129.         propName,
    
  130.         typeName(value),
    
  131.       );
    
  132.       return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
    
  133.     }
    
  134.   }
    
  135. }
    
  136. 
    
  137. export function checkHtmlStringCoercion(value: mixed): void | string {
    
  138.   if (__DEV__) {
    
  139.     if (willCoercionThrow(value)) {
    
  140.       console.error(
    
  141.         'The provided HTML markup uses a value of unsupported type %s.' +
    
  142.           ' This value must be coerced to a string before using it here.',
    
  143.         typeName(value),
    
  144.       );
    
  145.       return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
    
  146.     }
    
  147.   }
    
  148. }
    
  149. 
    
  150. export function checkFormFieldValueStringCoercion(value: mixed): void | string {
    
  151.   if (__DEV__) {
    
  152.     if (willCoercionThrow(value)) {
    
  153.       console.error(
    
  154.         'Form field values (value, checked, defaultValue, or defaultChecked props)' +
    
  155.           ' must be strings, not %s.' +
    
  156.           ' This value must be coerced to a string before using it here.',
    
  157.         typeName(value),
    
  158.       );
    
  159.       return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
    
  160.     }
    
  161.   }
    
  162. }