diff --git a/.circleci/config.yml b/.circleci/config.yml index cbec7d45ad74..695f7610ddfc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,11 +88,13 @@ jobs: if [ "<< parameters.react-version >>" == "^17.0.0" ]; then YARN_ENABLE_GLOBAL_CACHE=false yarn remove @react-navigation/native @react-navigation/native-stack react-native-screens YARN_ENABLE_GLOBAL_CACHE=false yarn workspace @data-client/react remove @react-navigation/native + YARN_ENABLE_GLOBAL_CACHE=false yarn workspaces foreach -W --include @data-client/img --include @data-client/react --include @data-client/ssr --include @data-client/test --include @data-client/use-enhanced-reducer add --dev react@<< parameters.react-version >> react-dom@<< parameters.react-version >> react-test-renderer@<< parameters.react-version >> YARN_ENABLE_GLOBAL_CACHE=false yarn add --dev react@<< parameters.react-version >> react-dom@<< parameters.react-version >> react-test-renderer@<< parameters.react-version >> @testing-library/react@^12.0.0 @testing-library/react-hooks @react-navigation/native@^6.0.0 @react-navigation/native-stack@^6.0.0 react-native-screens@^3.0.0 YARN_ENABLE_GLOBAL_CACHE=false yarn workspace @data-client/react add @react-navigation/native@^6.0.0 YARN_ENABLE_GLOBAL_CACHE=false yarn workspace @data-client/test add @testing-library/react@^12.0.0 elif [ "<< parameters.react-version >>" == "^18" ]; then YARN_ENABLE_GLOBAL_CACHE=false yarn add --dev react@<< parameters.react-version >> react-dom@<< parameters.react-version >> react-test-renderer@<< parameters.react-version >> + YARN_ENABLE_GLOBAL_CACHE=false yarn workspaces foreach -W --include @data-client/img --include @data-client/react --include @data-client/ssr --include @data-client/test --include @data-client/use-enhanced-reducer add --dev react@<< parameters.react-version >> react-dom@<< parameters.react-version >> react-test-renderer@<< parameters.react-version >> fi - run: command: | diff --git a/package.json b/package.json index cb0cd9e343ad..0ea89e9252b1 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@js-temporal/polyfill": "^0.4.4", "@react-navigation/native": "^7.0.0", "@react-navigation/native-stack": "^7.0.0", + "@testing-library/dom": "^10.4.0", "@testing-library/react": "16.0.1", "@testing-library/react-hooks": "8.0.1", "@testing-library/react-native": "12.9.0", @@ -87,8 +88,8 @@ "node-fetch": "^3.3.0", "npm-run-all": "^4.1.5", "prettier": "3.4.1", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "19.0.0", + "react-dom": "19.0.0", "react-native": "0.76.3", "react-native-safe-area-context": "^4.14.0", "react-native-screens": "^4.1.0", diff --git a/packages/img/package.json b/packages/img/package.json index e50a21a8e97a..6de4be59b82d 100644 --- a/packages/img/package.json +++ b/packages/img/package.json @@ -86,6 +86,7 @@ "@anansi/browserslist-config": "^1.4.2", "@data-client/react": "workspace:*", "@types/node": "^22.0.0", - "@types/react": "19.0.0" + "@types/react": "19.0.0", + "react": "19.0.0" } } diff --git a/packages/react/package.json b/packages/react/package.json index 77c1547f5538..171485939e31 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -192,6 +192,7 @@ "@types/qs": "^6", "@types/react": "19.0.0", "qs": "^6.13.1", + "react": "19.0.0", "react-native": "^0.76.0" } } diff --git a/packages/react/src/__tests__/integration-endpoint.web.tsx b/packages/react/src/__tests__/integration-endpoint.web.tsx index 581daad41eb7..180f3cb1e850 100644 --- a/packages/react/src/__tests__/integration-endpoint.web.tsx +++ b/packages/react/src/__tests__/integration-endpoint.web.tsx @@ -491,7 +491,8 @@ describe.each([ let data = result.current; expect(data).toBeInstanceOf(ArticleResource.get.schema); expect(data.title).toBe(temppayload.title); - expect(throws.length).toBe(1); + // react 19 suspends twice + expect(throws.length).toBeGreaterThanOrEqual(1); mynock .persist() @@ -544,7 +545,8 @@ describe.each([ let [data, { invalidate }] = result.current; expect(data).toBeInstanceOf(CoolerArticle); expect(data.title).toBe(temppayload.title); - expect(throws.length).toBe(1); + // react 19 suspends twice + expect(throws.length).toBeGreaterThanOrEqual(1); mynock .persist() @@ -553,7 +555,8 @@ describe.each([ act(() => { invalidate(CoolerArticleResource.get, { id: temppayload.id }); }); - expect(throws.length).toBe(2); + // react 19 suspends twice + expect(throws.length).toBeGreaterThanOrEqual(2); await waitForNextUpdate(); [data, { invalidate }] = result.current; expect(data).toBeInstanceOf(CoolerArticle); diff --git a/packages/react/src/components/DataProvider.tsx b/packages/react/src/components/DataProvider.tsx index e5b39d0c07b2..e2d2b67e6f95 100644 --- a/packages/react/src/components/DataProvider.tsx +++ b/packages/react/src/components/DataProvider.tsx @@ -51,12 +51,11 @@ See https://dataclient.io/docs/guides/ssr.`, ); } // contents of this component expected to be relatively stable - const controllerRef: React.MutableRefObject = - useRef(undefined); + const controllerRef: React.RefObject = useRef(undefined); if (!controllerRef.current) controllerRef.current = new Controller(); //TODO: bind all methods so destructuring works - const managersRef: React.MutableRefObject = useRef(managers); + const managersRef: React.RefObject = useRef(managers); if (!managersRef.current) managersRef.current = getDefaultManagers(); // run in a useEffect in DataStore diff --git a/packages/react/src/hooks/__tests__/useController/expireAll.tsx b/packages/react/src/hooks/__tests__/useController/expireAll.tsx index 178de96f37dd..becdf75f7996 100644 --- a/packages/react/src/hooks/__tests__/useController/expireAll.tsx +++ b/packages/react/src/hooks/__tests__/useController/expireAll.tsx @@ -117,7 +117,8 @@ describe('expireAll', () => { act(() => { controller.expireAll(CoolerArticleResource.get); }); - expect(throws.length).toBe(1); + // react 19 throws twice + expect(throws.length).toBeGreaterThanOrEqual(1); }); it('should *not* suspend when invalidIfStale is false', () => { diff --git a/packages/react/src/hooks/__tests__/useController/fetch.tsx b/packages/react/src/hooks/__tests__/useController/fetch.tsx index 93ce41c431c7..b26ab932ad86 100644 --- a/packages/react/src/hooks/__tests__/useController/fetch.tsx +++ b/packages/react/src/hooks/__tests__/useController/fetch.tsx @@ -291,19 +291,22 @@ describe.each([ const [data, fetch] = result.current; expect(data).toBeInstanceOf(CoolerArticle); expect(data?.title).toBe(temppayload.title); - expect(throws.length).toBe(1); + // react 19 suspends twice + expect(throws.length).toBeGreaterThanOrEqual(1); mynock .persist() .get(`/article-cooler/${temppayload.id}`) .reply(200, { ...temppayload, title: 'othertitle' }); - expect(throws.length).toBe(1); + // react 19 suspends twice + expect(throws.length).toBeGreaterThanOrEqual(1); await act(async () => { await fetch(FutureArticleResource.delete, temppayload.id); rerender({ id: null }); }); - expect(throws.length).toBe(1); + // react 19 suspends twice + expect(throws.length).toBeGreaterThanOrEqual(1); expect(result.current[0]).toBe(null); }); diff --git a/packages/react/src/hooks/__tests__/useSuspense.web.tsx b/packages/react/src/hooks/__tests__/useSuspense.web.tsx index fb07b53ba722..3fd246f38123 100644 --- a/packages/react/src/hooks/__tests__/useSuspense.web.tsx +++ b/packages/react/src/hooks/__tests__/useSuspense.web.tsx @@ -64,9 +64,14 @@ async function testDispatchFetch( ); render(tree); expect(dispatch).toHaveBeenCalled(); - expect(dispatch.mock.calls.length).toBe(payloads.length); + // react 19 suspends twice + expect(dispatch.mock.calls.length).toBeGreaterThanOrEqual(payloads.length); let i = 0; for (const call of dispatch.mock.calls as any) { + // react 19, skip every other + if (Number(React.version.substring(0, 3)) >= 19 && i % 2 === 1) { + continue; + } expect(call[0].type).toBe(actionTypes.FETCH); delete call[0]?.meta?.fetchedAt; delete call[0]?.meta?.promise; diff --git a/packages/ssr/package.json b/packages/ssr/package.json index 739d320453cf..be6ef2b82c44 100644 --- a/packages/ssr/package.json +++ b/packages/ssr/package.json @@ -118,7 +118,7 @@ "peerDependencies": { "@data-client/react": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0", "@data-client/redux": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0", - "@types/react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react": "^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0", "next": ">=12.0.0", "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "redux": "^4.0.0 || ^5.0.0" @@ -139,7 +139,8 @@ "@types/react": "19.0.0", "@types/react-dom": "19.0.0", "next": "^15.0.0", - "react-dom": "^19.0.0", + "react": "19.0.0", + "react-dom": "19.0.0", "redux": "^5.0.0" } } diff --git a/packages/test/package.json b/packages/test/package.json index a27e0140ef1d..0c5edd8f2aa3 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -158,6 +158,8 @@ "@types/react": "19.0.0", "@types/react-dom": "19.0.0", "@types/react-test-renderer": "19.0.0", - "jest": "^29.5.0" + "jest": "^29.5.0", + "react": "19.0.0", + "react-dom": "19.0.0" } } diff --git a/yarn.lock b/yarn.lock index 3c423379a41e..7be1990997d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3149,6 +3149,7 @@ __metadata: "@data-client/react": "workspace:*" "@types/node": "npm:^22.0.0" "@types/react": "npm:19.0.0" + react: "npm:19.0.0" peerDependencies: "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 "@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0 @@ -3192,6 +3193,7 @@ __metadata: "@types/qs": "npm:^6" "@types/react": "npm:19.0.0" qs: "npm:^6.13.1" + react: "npm:19.0.0" react-native: "npm:^0.76.0" peerDependencies: "@react-navigation/native": ^6.0.0 || ^7.0.0 @@ -3247,12 +3249,13 @@ __metadata: "@types/react": "npm:19.0.0" "@types/react-dom": "npm:19.0.0" next: "npm:^15.0.0" - react-dom: "npm:^19.0.0" + react: "npm:19.0.0" + react-dom: "npm:19.0.0" redux: "npm:^5.0.0" peerDependencies: "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 "@data-client/redux": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 - "@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + "@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0 next: ">=12.0.0" react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 redux: ^4.0.0 || ^5.0.0 @@ -3280,6 +3283,8 @@ __metadata: "@types/react-dom": "npm:19.0.0" "@types/react-test-renderer": "npm:19.0.0" jest: "npm:^29.5.0" + react: "npm:19.0.0" + react-dom: "npm:19.0.0" peerDependencies: "@data-client/react": ^0.12.15 || ^0.13.0 || ^0.14.0 "@testing-library/react-hooks": ^8.0.0 @@ -6534,7 +6539,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^10.1.0": +"@testing-library/dom@npm:^10.1.0, @testing-library/dom@npm:^10.4.0": version: 10.4.0 resolution: "@testing-library/dom@npm:10.4.0" dependencies: @@ -25235,7 +25240,7 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^19.0.0": +"react-dom@npm:19.0.0, react-dom@npm:^19.0.0": version: 19.0.0 resolution: "react-dom@npm:19.0.0" dependencies: @@ -25550,7 +25555,7 @@ __metadata: languageName: node linkType: hard -"react@npm:^19.0.0": +"react@npm:19.0.0, react@npm:^19.0.0": version: 19.0.0 resolution: "react@npm:19.0.0" checksum: 10c0/9cad8f103e8e3a16d15cb18a0d8115d8bd9f9e1ce3420310aea381eb42aa0a4f812cf047bb5441349257a05fba8a291515691e3cb51267279b2d2c3253f38471 @@ -26727,6 +26732,7 @@ __metadata: "@js-temporal/polyfill": "npm:^0.4.4" "@react-navigation/native": "npm:^7.0.0" "@react-navigation/native-stack": "npm:^7.0.0" + "@testing-library/dom": "npm:^10.4.0" "@testing-library/react": "npm:16.0.1" "@testing-library/react-hooks": "npm:8.0.1" "@testing-library/react-native": "npm:12.9.0" @@ -26754,8 +26760,8 @@ __metadata: node-fetch: "npm:^3.3.0" npm-run-all: "npm:^4.1.5" prettier: "npm:3.4.1" - react: "npm:^19.0.0" - react-dom: "npm:^19.0.0" + react: "npm:19.0.0" + react-dom: "npm:19.0.0" react-native: "npm:0.76.3" react-native-safe-area-context: "npm:^4.14.0" react-native-screens: "npm:^4.1.0"