diff --git a/.changeset/hungry-kids-study.md b/.changeset/hungry-kids-study.md new file mode 100644 index 00000000..2aebe8a0 --- /dev/null +++ b/.changeset/hungry-kids-study.md @@ -0,0 +1,5 @@ +--- +'@remote-dom/react': minor +--- + +Add React 19 compatibility and update ref handling for cross-version support. diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 514c1420..6c693a4b 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,11 @@ # @remote-dom/react +## Unreleased + +### Patch Changes + +- Add React 19 compatibility and update ref handling for cross-version support. + ## 1.2.2 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 7f5e128d..71e1b18f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -69,11 +69,11 @@ }, "dependencies": { "@remote-dom/core": "workspace:^1.7.0", - "@types/react": "^18.0.0", + "@types/react": "^18.0.0 || ^19.0.0", "htm": "^3.1.1" }, "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "react": { @@ -82,9 +82,9 @@ }, "devDependencies": { "@quilted/react-testing": "^0.6.11", - "@types/react-dom": "^18.3.0", - "react": "^18.3.0", - "react-dom": "^18.3.0" + "@types/react-dom": "^19.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "browserslist": [ "defaults and not dead" diff --git a/packages/react/source/component.tsx b/packages/react/source/component.tsx index f0b0ba0e..f19372ec 100644 --- a/packages/react/source/component.tsx +++ b/packages/react/source/component.tsx @@ -122,8 +122,10 @@ export function createRemoteComponent< InstanceType, RemoteComponentPropsFromElementConstructor >(function RemoteComponent(props, ref) { - const internalRef = useRef(); - const lastRemotePropertiesRef = useRef>(); + const internalRef = useRef(undefined); + const lastRemotePropertiesRef = useRef | undefined>( + undefined, + ); const remoteProperties: Record = {}; const children = toChildren(props.children); @@ -195,7 +197,7 @@ export function createRemoteComponent< { ref: (refValue: any) => { internalRef.current = refValue; - if (typeof ref === 'function') ref(refValue); + if (typeof ref === 'function') return ref(refValue); else if (ref != null) ref.current = refValue; }, }, diff --git a/packages/react/source/host/component.tsx b/packages/react/source/host/component.tsx index fbd93742..0cc8e78b 100644 --- a/packages/react/source/host/component.tsx +++ b/packages/react/source/host/component.tsx @@ -1,11 +1,12 @@ import type {RemoteReceiverElement} from '@remote-dom/core/receivers'; -import { - memo, - useRef, - useEffect, - type MutableRefObject, - type ComponentType, -} from 'react'; +import {memo, useRef, useEffect, type ComponentType} from 'react'; + +// Compat type: mutable ref object that works across @types/react 17, 18, and 19. +// Matches runtime shape used by React refs in all supported versions. +// TODO: When React < 19 support is dropped, replace with `import { type RefObject } from 'react'`. +interface MutableRef { + current: T; +} import {useRemoteReceived} from './hooks/remote-received.ts'; import { @@ -65,7 +66,7 @@ export interface RemoteComponentRendererOptions interface Internals extends Pick { id: string; - instanceRef: MutableRefObject; + instanceRef: MutableRef; } /** @@ -85,7 +86,7 @@ export function createRemoteComponentRenderer< receiver, components, }: RemoteComponentRendererProps) { - const internalsRef = useRef(); + const internalsRef = useRef(undefined); const attachedElement = useRemoteReceived(element, receiver); const resolvedElement = attachedElement ?? element; @@ -140,7 +141,7 @@ export function createRemoteComponentRenderer< function createImplementationRef( internals: Pick, -): MutableRefObject { +): MutableRef { let current: unknown = null; return { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4b8fe4d..192d8e67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,7 +194,7 @@ importers: specifier: workspace:^1.7.0 version: link:../core '@types/react': - specifier: ^18.0.0 + specifier: ^18.0.0 || ^19.0.0 version: 18.3.5 htm: specifier: ^3.1.1 @@ -202,16 +202,16 @@ importers: devDependencies: '@quilted/react-testing': specifier: ^0.6.11 - version: 0.6.11(preact@10.23.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.6.11(preact@10.23.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.0 + specifier: ^19.0.0 + version: 19.2.3(@types/react@18.3.5) react: - specifier: ^18.3.0 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.0 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/signals: dependencies: @@ -1665,6 +1665,11 @@ packages: '@types/react-dom@18.3.0': resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react-reconciler@0.28.8': resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==} @@ -2817,6 +2822,11 @@ packages: peerDependencies: react: ^18.3.1 + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -2837,6 +2847,10 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -2938,6 +2952,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4734,14 +4751,14 @@ snapshots: optionalDependencies: preact: 10.23.2 - '@quilted/react-testing@0.6.11(preact@10.23.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@quilted/react-testing@0.6.11(preact@10.23.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@quilted/preact-testing': 0.1.7(preact@10.23.2) jest-matcher-utils: 27.5.1 optionalDependencies: preact: 10.23.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) '@quilted/request-router@0.3.0': dependencies: @@ -5040,6 +5057,10 @@ snapshots: dependencies: '@types/react': 18.3.5 + '@types/react-dom@19.2.3(@types/react@18.3.5)': + dependencies: + '@types/react': 18.3.5 + '@types/react-reconciler@0.28.8': dependencies: '@types/react': 18.3.5 @@ -6191,6 +6212,11 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + react-is@17.0.2: {} react-is@18.2.0: {} @@ -6208,6 +6234,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + react@19.2.4: {} + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -6327,6 +6355,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + scheduler@0.27.0: {} + semver@6.3.1: {} semver@7.5.4: