diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index b9ec688d877ed..76135f96b4249 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -2327,7 +2327,7 @@ function codegenJsxAttribute( } } -const JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN = /[<>&]/; +const JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN = /[<>&{}]/; function codegenJsxElement( cx: Context, place: Place, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-bracket-in-text.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-bracket-in-text.expect.md new file mode 100644 index 0000000000000..21a2f31ccdbba --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-bracket-in-text.expect.md @@ -0,0 +1,51 @@ + +## Input + +```javascript +function Test() { + return ( +
+ If the string contains the string {pageNumber} it will be + replaced by the page number. +
+ ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Test, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Test() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ( +
+ { + "If the string contains the string {pageNumber} it will be replaced by the page number." + } +
+ ); + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Test, + params: [], +}; + +``` + +### Eval output +(kind: ok)
If the string contains the string {pageNumber} it will be replaced by the page number.
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-bracket-in-text.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-bracket-in-text.jsx new file mode 100644 index 0000000000000..1e93f5e0ad8f9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-bracket-in-text.jsx @@ -0,0 +1,13 @@ +function Test() { + return ( +
+ If the string contains the string {pageNumber} it will be + replaced by the page number. +
+ ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Test, + params: [], +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/README.md b/compiler/packages/eslint-plugin-react-compiler/README.md index cc70679383690..3bf175f85e873 100644 --- a/compiler/packages/eslint-plugin-react-compiler/README.md +++ b/compiler/packages/eslint-plugin-react-compiler/README.md @@ -29,7 +29,7 @@ import react from "eslint-plugin-react" export default [ // Your existing config { ...pluginReact.configs.flat.recommended, settings: { react: { version: "detect" } } }, -+ reactCompiler.config.recommended ++ reactCompiler.configs.recommended ] ``` diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 4ea66dcddd742..3d125cc3104ea 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -16,6 +16,7 @@ import type { ReactTimeInfo, ReactStackTrace, ReactCallSite, + ReactErrorInfoDev, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; @@ -2123,18 +2124,12 @@ function resolveErrorProd(response: Response): Error { function resolveErrorDev( response: Response, - errorInfo: { - name: string, - message: string, - stack: ReactStackTrace, - env: string, - ... - }, + errorInfo: ReactErrorInfoDev, ): Error { - const name: string = errorInfo.name; - const message: string = errorInfo.message; - const stack: ReactStackTrace = errorInfo.stack; - const env: string = errorInfo.env; + const name = errorInfo.name; + const message = errorInfo.message; + const stack = errorInfo.stack; + const env = errorInfo.env; if (!__DEV__) { // These errors should never make it into a build so we don't need to encode them in codes.json diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 44b0c76c3f4fb..25bbd0e83acba 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -1387,6 +1387,7 @@ describe('ReactFlight', () => { errors: [ { message: 'This is an error', + name: 'Error', stack: expect.stringContaining( 'Error: This is an error\n' + ' at eval (eval at testFunction (inspected-page.html:29:11),%20%3Canonymous%3E:1:35)\n' + diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 2e1fcb19a6782..f30489b7f655f 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -127,6 +127,13 @@ function getPrimitiveStackCache(): Map> { } Dispatcher.useId(); + + if (typeof Dispatcher.useResourceEffect === 'function') { + Dispatcher.useResourceEffect(() => ({}), []); + } + if (typeof Dispatcher.useEffectEvent === 'function') { + Dispatcher.useEffectEvent((args: empty) => {}); + } } finally { readHookLog = hookLog; hookLog = []; @@ -749,6 +756,20 @@ function useResourceEffect( }); } +function useEffectEvent) => mixed>(callback: F): F { + nextHook(); + hookLog.push({ + displayName: null, + primitive: 'EffectEvent', + stackError: new Error(), + value: callback, + debugInfo: null, + dispatcherHookName: 'EffectEvent', + }); + + return callback; +} + const Dispatcher: DispatcherType = { use, readContext, @@ -773,6 +794,7 @@ const Dispatcher: DispatcherType = { useFormState, useActionState, useHostTransitionStatus, + useEffectEvent, useResourceEffect, }; @@ -962,7 +984,7 @@ function parseHookName(functionName: void | string): string { startIndex += 'unstable_'.length; } - if (functionName.slice(startIndex).startsWith('unstable_')) { + if (functionName.slice(startIndex).startsWith('experimental_')) { startIndex += 'experimental_'.length; } diff --git a/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js b/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js index abc6eee336c19..95f104925b93d 100644 --- a/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js +++ b/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js @@ -19,6 +19,7 @@ import NestedProps from './NestedProps'; import SimpleValues from './SimpleValues'; import SymbolKeys from './SymbolKeys'; import UseMemoCache from './UseMemoCache'; +import UseEffectEvent from './UseEffectEvent'; // TODO Add Immutable JS example @@ -36,6 +37,7 @@ export default function InspectableElements(): React.Node { + ); } diff --git a/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js b/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js new file mode 100644 index 0000000000000..e55f6a3c55e4f --- /dev/null +++ b/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js @@ -0,0 +1,32 @@ +import * as React from 'react'; + +const {experimental_useEffectEvent, useState, useEffect} = React; + +export default function UseEffectEvent(): React.Node { + return ( + <> + + + + ); +} + +function SingleHookCase() { + const onClick = experimental_useEffectEvent(() => {}); + + return
; +} + +function useCustomHook() { + const [state, setState] = useState(); + const onClick = experimental_useEffectEvent(() => {}); + useEffect(() => {}); + + return [state, setState, onClick]; +} + +function HookTreeCase() { + const onClick = useCustomHook(); + + return
; +} diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index cda88fc5c1718..0c7d12219c32c 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -64,6 +64,8 @@ import type { ReactTimeInfo, ReactStackTrace, ReactCallSite, + ReactErrorInfo, + ReactErrorInfoDev, } from 'shared/ReactTypes'; import type {ReactElement} from 'shared/ReactElementType'; import type {LazyComponent} from 'react/src/ReactLazy'; @@ -3093,8 +3095,8 @@ function emitPostponeChunk( function serializeErrorValue(request: Request, error: Error): string { if (__DEV__) { - let name; - let message; + let name: string = 'Error'; + let message: string; let stack: ReactStackTrace; let env = (0, request.environmentName)(); try { @@ -3112,7 +3114,7 @@ function serializeErrorValue(request: Request, error: Error): string { message = 'An error occurred but serializing the error message failed.'; stack = []; } - const errorInfo = {name, message, stack, env}; + const errorInfo: ReactErrorInfoDev = {name, message, stack, env}; const id = outlineModel(request, errorInfo); return '$Z' + id.toString(16); } else { @@ -3129,13 +3131,15 @@ function emitErrorChunk( digest: string, error: mixed, ): void { - let errorInfo: any; + let errorInfo: ReactErrorInfo; if (__DEV__) { - let message; + let name: string = 'Error'; + let message: string; let stack: ReactStackTrace; let env = (0, request.environmentName)(); try { if (error instanceof Error) { + name = error.name; // eslint-disable-next-line react-internal/safe-string-coercion message = String(error.message); stack = filterStackTrace(request, error, 0); @@ -3157,7 +3161,7 @@ function emitErrorChunk( message = 'An error occurred but serializing the error message failed.'; stack = []; } - errorInfo = {digest, message, stack, env}; + errorInfo = {digest, name, message, stack, env}; } else { errorInfo = {digest}; } diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 26cd57fc96f01..735f96a36f509 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -203,6 +203,20 @@ export type ReactEnvironmentInfo = { +env: string, }; +export type ReactErrorInfoProd = { + +digest: string, +}; + +export type ReactErrorInfoDev = { + +digest?: string, + +name: string, + +message: string, + +stack: ReactStackTrace, + +env: string, +}; + +export type ReactErrorInfo = ReactErrorInfoProd | ReactErrorInfoDev; + export type ReactAsyncInfo = { +type: string, // Stashed Data for the Specific Execution Environment. Not part of the transport protocol