diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.input.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.input.tsx
new file mode 100644
index 0000000000..363af35b13
--- /dev/null
+++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.input.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react'
+import {
+ Hydrate,
+ QueryClient,
+ QueryClientProvider,
+} from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
+
+export default function MyApp({ Component, pageProps }) {
+ const [queryClient] = React.useState(() => new QueryClient())
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.output.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.output.tsx
new file mode 100644
index 0000000000..cbedc7ca1a
--- /dev/null
+++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/default-import.output.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react'
+import {
+ HydrationBoundary,
+ QueryClient,
+ QueryClientProvider,
+} from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
+
+export default function MyApp({ Component, pageProps }) {
+ const [queryClient] = React.useState(() => new QueryClient())
+
+ return (
+ (
+
+
+
+
+ )
+ );
+}
diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.input.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.input.tsx
new file mode 100644
index 0000000000..00c9d988af
--- /dev/null
+++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.input.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react'
+import {
+ Hydrate as RenamedHydrate,
+ QueryClient as RenamedQueryClient,
+ QueryClientProvider as RenamedQueryClientProvider,
+} from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
+
+export default function MyApp({ Component, pageProps }) {
+ const [queryClient] = React.useState(() => new RenamedQueryClient())
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.output.tsx b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.output.tsx
new file mode 100644
index 0000000000..f83abc1e4d
--- /dev/null
+++ b/packages/codemods/src/v5/rename-hydrate/__testfixtures__/named-import.output.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react'
+import {
+ HydrationBoundary as RenamedHydrate,
+ QueryClient as RenamedQueryClient,
+ QueryClientProvider as RenamedQueryClientProvider,
+} from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
+
+export default function MyApp({ Component, pageProps }) {
+ const [queryClient] = React.useState(() => new RenamedQueryClient())
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/packages/codemods/src/v5/rename-hydrate/__tests__/rename-hydrate.test.js b/packages/codemods/src/v5/rename-hydrate/__tests__/rename-hydrate.test.js
new file mode 100644
index 0000000000..600e852553
--- /dev/null
+++ b/packages/codemods/src/v5/rename-hydrate/__tests__/rename-hydrate.test.js
@@ -0,0 +1,10 @@
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const defineTest = require('jscodeshift/dist/testUtils').defineTest
+
+defineTest(__dirname, 'rename-hydrate', null, 'default-import', {
+ parser: 'tsx',
+})
+
+defineTest(__dirname, 'rename-hydrate', null, 'named-import', {
+ parser: 'tsx',
+})
diff --git a/packages/codemods/src/v5/rename-hydrate/rename-hydrate.js b/packages/codemods/src/v5/rename-hydrate/rename-hydrate.js
new file mode 100644
index 0000000000..488040d6a2
--- /dev/null
+++ b/packages/codemods/src/v5/rename-hydrate/rename-hydrate.js
@@ -0,0 +1,55 @@
+module.exports = (file, api) => {
+ const jscodeshift = api.jscodeshift
+ const root = jscodeshift(file.source)
+
+ const importSpecifiers = root
+ .find(jscodeshift.ImportDeclaration, {
+ source: {
+ value: '@tanstack/react-query',
+ },
+ })
+ .find(jscodeshift.ImportSpecifier, {
+ imported: {
+ name: 'Hydrate',
+ },
+ })
+
+ if (importSpecifiers.length > 0) {
+ const names = {
+ searched: 'Hydrate', // By default, we want to replace the `Hydrate` usages.
+ target: 'HydrationBoundary', // We want to replace them with `HydrationBoundary`.
+ }
+
+ importSpecifiers.replaceWith(({ node: mutableNode }) => {
+ /**
+ * When the local and imported names match which means the code doesn't contain import aliases, we need
+ * to replace only the import specifier.
+ * @type {boolean}
+ */
+ const usesDefaultImport =
+ mutableNode.local.name === mutableNode.imported.name
+
+ if (!usesDefaultImport) {
+ // If the code uses import aliases, we must re-use the alias.
+ names.searched = mutableNode.local.name
+ names.target = mutableNode.local.name
+ }
+
+ // Override the import specifier.
+ mutableNode.imported.name = 'HydrationBoundary'
+
+ return mutableNode
+ })
+
+ root
+ .findJSXElements(names.searched)
+ .replaceWith(({ node: mutableNode }) => {
+ mutableNode.openingElement.name.name = names.target
+ mutableNode.closingElement.name.name = names.target
+
+ return mutableNode
+ })
+ }
+
+ return root.toSource({ quote: 'single', lineTerminator: '\n' })
+}