Update to React 19 (#321)

* Upgrade to react 19

* Change waitFor timeout and remove uneeded waitFor usage. Upgrade use-sync-external-store

* sync cpackage-lock.json

* Add removed types again
This commit is contained in:
Roger Veciana i Rovira 2025-01-14 15:51:55 +01:00 committed by GitHub
parent 34ceb9362a
commit 22e5866eeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 5759 additions and 6141 deletions

11757
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,27 +41,27 @@
"trailingComma": "all"
},
"devDependencies": {
"@babel/preset-env": "^7.22.7",
"@babel/preset-typescript": "^7.22.5",
"@testing-library/react": "^14.0.0",
"@types/node": "^20.4.7",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitest/coverage-v8": "^0.33.0",
"esbuild": "^0.18.11",
"@babel/preset-env": "^7.26.0",
"@babel/preset-typescript": "^7.26.0",
"@testing-library/react": "^16.1.0",
"@types/node": "^22.10.5",
"@types/react": "^19.0.4",
"@types/react-dom": "^19.0.2",
"@vitest/coverage-v8": "^2.1.8",
"esbuild": "^0.24.2",
"expose-gc": "^1.0.0",
"husky": ">=8.0.3",
"jest-environment-jsdom": "^29.6.1",
"jsdom": "^22.1.0",
"lint-staged": ">=13.2.3",
"prettier": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^26.0.0",
"lint-staged": "^15.3.0",
"prettier": "^3.4.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-test-renderer": "^19.0.0",
"rxjs": "^7.8.1",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
"vitest": "^0.33.0"
"tslib": "^2.8.1",
"typescript": "^5.7.3",
"vitest": "^2.1.8"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,md}": "prettier --write"

View File

@ -53,9 +53,9 @@
],
"dependencies": {
"@rx-state/core": "0.1.4",
"use-sync-external-store": "^1.0.0"
"use-sync-external-store": "^1.4.0"
},
"devDependencies": {
"@types/use-sync-external-store": "^0.0.3"
"@types/use-sync-external-store": "^0.0.6"
}
}

View File

@ -45,10 +45,13 @@ export const Subscribe: React.FC<{
source$?: Observable<any>
fallback?: NonNullable<ReactNode> | null
}> = ({ source$, children, fallback }) => {
const subscriptionRef = useRef<{
s: Subscription
u: (source: StateObservable<any>) => void
}>()
const subscriptionRef = useRef<
| {
s: Subscription
u: (source: StateObservable<any>) => void
}
| undefined
>(undefined)
if (!subscriptionRef.current) {
const s = new Subscription()

View File

@ -89,8 +89,13 @@ describe("connectFactoryObservable", () => {
await wait(110)
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
vi.waitFor(
() => {
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
},
{ timeout: 2000 },
)
})
it("synchronously mounts the emitted value if the observable emits synchronously", () => {
@ -311,7 +316,7 @@ describe("connectFactoryObservable", () => {
})
it("allows errors to be caught in error boundaries", () => {
const errStream = new Subject()
const errStream = new Subject<any>()
const [useError] = bind(() => errStream, 1)
const ErrorComponent = () => {
@ -338,7 +343,7 @@ describe("connectFactoryObservable", () => {
})
it("allows sync errors to be caught in error boundaries with suspense", () => {
const errStream = new Observable((observer) =>
const errStream = new Observable<any>((observer) =>
observer.error("controlled error"),
)
const [useError, getErrStream$] = bind((_: string) => errStream)
@ -369,7 +374,7 @@ describe("connectFactoryObservable", () => {
})
it("allows async errors to be caught in error boundaries with suspense", async () => {
const errStream = new Subject()
const errStream = new Subject<any>()
const [useError, getErrStream$] = bind((_: string) => errStream)
const ErrorComponent = () => {

View File

@ -1,11 +1,13 @@
import {
act,
act as componentAct,
fireEvent,
render,
renderHook,
screen,
} from "@testing-library/react"
import { act, renderHook } from "@testing-library/react"
import React, { FC, StrictMode, Suspense, useEffect, useState } from "react"
import { renderToPipeableStream } from "react-dom/server"
import {
defer,
EMPTY,
@ -26,7 +28,7 @@ import {
startWith,
switchMapTo,
} from "rxjs/operators"
import { describe, it, beforeAll, afterAll, expect, vi } from "vitest"
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"
import {
bind,
sinkSuspense,
@ -34,9 +36,8 @@ import {
SUSPENSE,
useStateObservable,
} from "../"
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
import { renderToPipeableStream } from "react-dom/server"
import { pipeableStreamToObservable } from "../test-helpers/pipeableStreamToObservable"
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms))
@ -93,8 +94,13 @@ describe("connectObservable", () => {
await wait(110)
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
vi.waitFor(
() => {
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
},
{ timeout: 2000 },
)
sub.unsubscribe()
})
@ -118,8 +124,13 @@ describe("connectObservable", () => {
await wait(110)
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
vi.waitFor(
() => {
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
},
{ timeout: 2000 },
)
sub.unsubscribe()
})
@ -183,9 +194,7 @@ describe("connectObservable", () => {
const [useNumber] = bind(numberStream, 1)
const [useString] = bind(stringStream, "a")
const BatchComponent: FC<{
onUpdate: () => void
}> = ({ onUpdate }) => {
const BatchComponent: FC<{ onUpdate: () => void }> = ({ onUpdate }) => {
const number = useNumber()
const string = useString()
useEffect(onUpdate)
@ -329,8 +338,8 @@ describe("connectObservable", () => {
{value === null
? "default"
: value instanceof Promise
? "promise"
: "wtf?"}
? "promise"
: "wtf?"}
</div>
)
}
@ -387,13 +396,17 @@ describe("connectObservable", () => {
await wait(10)
expect(screen.queryByText("Waiting")).toBeNull()
expect(screen.queryByText("Result 1")).not.toBeNull()
vi.waitFor(() => {
expect(screen.queryByText("Waiting")).toBeNull()
expect(screen.queryByText("Result 1")).not.toBeNull()
})
fireEvent.click(screen.getByText(/NextVal/i))
expect(screen.queryByText("Result 2")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
vi.waitFor(() => {
expect(screen.queryByText("Result 2")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
})
fireEvent.click(screen.getByText(/NextKey/i))
@ -405,17 +418,21 @@ describe("connectObservable", () => {
await wait(10)
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
vi.waitFor(() => {
expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
})
fireEvent.click(screen.getByText(/NextVal/i))
expect(screen.queryByText("Result 2")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
vi.waitFor(() => {
expect(screen.queryByText("Result 2")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
})
})
it("allows errors to be caught in error boundaries", () => {
const errStream = new Subject()
const errStream = new Subject<any>()
const [useError] = bind(errStream, 1)
const ErrorComponent = () => {
@ -441,7 +458,7 @@ describe("connectObservable", () => {
})
it("allows sync errors to be caught in error boundaries with suspense, using source$", () => {
const errStream = new Observable((observer) =>
const errStream = new Observable<any>((observer) =>
observer.error("controlled error"),
)
const [useError, errStream$] = bind(errStream)
@ -468,7 +485,7 @@ describe("connectObservable", () => {
})
it("allows sync errors to be caught in error boundaries with suspense, without using source$", () => {
const errStream = new Observable((observer) =>
const errStream = new Observable<any>((observer) =>
observer.error("controlled error"),
)
const [useError] = bind(errStream)
@ -495,7 +512,7 @@ describe("connectObservable", () => {
})
it("allows sync errors to be caught in error boundaries when there is a default value", () => {
const errStream = new Observable((observer) =>
const errStream = new Observable<any>((observer) =>
observer.error("controlled error"),
)
const [useError, errStream$] = bind(errStream, 0)
@ -522,7 +539,7 @@ describe("connectObservable", () => {
})
it("allows async errors to be caught in error boundaries with suspense", async () => {
const errStream = new Subject()
const errStream = new Subject<any>()
const [useError, errStream$] = bind(errStream)
const errStream$WithoutErrors = errStream$.pipe(catchError(() => NEVER))
@ -702,7 +719,7 @@ describe("connectObservable", () => {
})
it("should throw an error if the stream completes without emitting while on SUSPENSE", async () => {
const subject = new Subject()
const subject = new Subject<any>()
const [useValue, value$] = bind(subject)
const errorCallback = vi.fn()

View File

@ -26,7 +26,7 @@ export const useStateObservable = <O>(
): Exclude<O, typeof SUSPENSE> => {
const subscription = useSubscription()
const [, setError] = useState()
const callbackRef = useRef<Ref<O>>()
const callbackRef = useRef<Ref<O> | undefined>(undefined)
if (!callbackRef.current) {
const getValue = (src: StateObservable<O>) => {

View File

@ -79,8 +79,8 @@ export function createKeyedSignal<K, T, A extends any[]>(
const payload = mapper
? mapper(...args)
: args.length === 2
? args[1]
: args[0]
? args[1]
: args[0]
const key = keySelector ? keySelector(payload) : args[0]
observersMap.get(key)?.forEach((o) => {
o.next(payload)