diff --git a/packages/plugins/eslint-plugin-react-x/README.md b/packages/plugins/eslint-plugin-react-x/README.md
index ac4f11316..ecb80ca8a 100644
--- a/packages/plugins/eslint-plugin-react-x/README.md
+++ b/packages/plugins/eslint-plugin-react-x/README.md
@@ -65,6 +65,7 @@ export default [
"react-x/no-unstable-default-props": "warn",
"react-x/no-unused-class-component-members": "warn",
"react-x/no-unused-state": "warn",
+ "react-x/no-use-context": "warn",
"react-x/use-jsx-vars": "warn",
},
},
diff --git a/packages/plugins/eslint-plugin-react-x/src/index.ts b/packages/plugins/eslint-plugin-react-x/src/index.ts
index 57d74ad15..f62dcff06 100644
--- a/packages/plugins/eslint-plugin-react-x/src/index.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/index.ts
@@ -42,6 +42,7 @@ import noUnstableContextValue from "./rules/no-unstable-context-value";
import noUnstableDefaultProps from "./rules/no-unstable-default-props";
import noUnusedClassComponentMembers from "./rules/no-unused-class-component-members";
import noUnusedState from "./rules/no-unused-state";
+import noUseContext from "./rules/no-use-context";
import noUselessFragment from "./rules/no-useless-fragment";
import preferDestructuringAssignment from "./rules/prefer-destructuring-assignment";
import preferReactNamespaceImport from "./rules/prefer-react-namespace-import";
@@ -99,6 +100,7 @@ export default {
"no-unstable-default-props": noUnstableDefaultProps,
"no-unused-class-component-members": noUnusedClassComponentMembers,
"no-unused-state": noUnusedState,
+ "no-use-context": noUseContext,
"no-useless-fragment": noUselessFragment,
"prefer-destructuring-assignment": preferDestructuringAssignment,
"prefer-react-namespace-import": preferReactNamespaceImport,
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.spec.ts
new file mode 100644
index 000000000..ae36f51ff
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.spec.ts
@@ -0,0 +1,188 @@
+import { ruleTester } from "../../../../../test";
+import rule, { RULE_NAME } from "./no-use-context";
+
+ruleTester.run(RULE_NAME, rule, {
+ invalid: [
+ {
+ code: /* tsx */ `
+ import { useContext } from 'react'
+
+ export const Component = () => {
+ const value = useContext(MyContext)
+ return
{value}
+ }
+ `,
+ errors: [
+ { messageId: "noUseContext" },
+ { messageId: "noUseContext" },
+ ],
+ output: /* tsx */ `
+ import { use } from 'react'
+
+ export const Component = () => {
+ const value = use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ import { useContext } from 'react'
+
+ export const Component = () => {
+ const value = useContext(MyContext)
+ return {value}
+ }
+ `,
+ errors: [
+ { messageId: "noUseContext" },
+ { messageId: "noUseContext" },
+ ],
+ output: /* tsx */ `
+ import { use } from 'react'
+
+ export const Component = () => {
+ const value = use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ import { use, useContext } from 'react'
+
+ export const Component = () => {
+ const value = useContext(MyContext)
+ return {value}
+ }
+ `,
+ errors: [
+ { messageId: "noUseContext" },
+ { messageId: "noUseContext" },
+ ],
+ output: /* tsx */ `
+ import { use, } from 'react'
+
+ export const Component = () => {
+ const value = use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ import React from 'react'
+
+ export const Component = () => {
+ const value = React.useContext(MyContext)
+ return {value}
+ }
+ `,
+ errors: [
+ { messageId: "noUseContext" },
+ ],
+ output: /* tsx */ `
+ import React from 'react'
+
+ export const Component = () => {
+ const value = React.use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ import { use, useContext as useCtx } from 'react'
+
+ export const Component = () => {
+ const value = useCtx(MyContext)
+ return {value}
+ }
+ `,
+ errors: [
+ { messageId: "noUseContext" },
+ { messageId: "noUseContext" },
+ ],
+ output: /* tsx */ `
+ import { use, useContext as useCtx } from 'react'
+
+ export const Component = () => {
+ const value = use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ ],
+ valid: [
+ {
+ code: /* tsx */ `
+ import { useContext } from 'react'
+
+ export const Component = () => {
+ const value = useContext(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "18.3.1",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ import { use } from 'react'
+
+ export const Component = () => {
+ const value = use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ import React from 'react'
+
+ export const Component = () => {
+ const value = React.use(MyContext)
+ return {value}
+ }
+ `,
+ settings: {
+ "react-x": {
+ version: "19.0.0",
+ },
+ },
+ },
+ ],
+});
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.ts
new file mode 100644
index 000000000..2094c4716
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.ts
@@ -0,0 +1,100 @@
+import { isReactHookCall, isReactHookCallWithNameAlias } from "@eslint-react/core";
+import type { RuleFeature } from "@eslint-react/shared";
+import { getSettingsFromContext } from "@eslint-react/shared";
+import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
+import { compare } from "compare-versions";
+import type { CamelCase } from "string-ts";
+import { isMatching } from "ts-pattern";
+
+import { createRule } from "../utils";
+
+export const RULE_NAME = "no-use-context";
+
+export const RULE_FEATURES = [
+ "CHK",
+ "MOD",
+] as const satisfies RuleFeature[];
+
+export type MessageID = CamelCase;
+
+export default createRule<[], MessageID>({
+ meta: {
+ type: "problem",
+ docs: {
+ description: "disallow the use of 'useContext'",
+ [Symbol.for("rule_features")]: RULE_FEATURES,
+ },
+ fixable: "code",
+ messages: {
+ noUseContext: "In React 19, 'use' is preferred over 'useContext' because it is more flexible.",
+ },
+ schema: [],
+ },
+ name: RULE_NAME,
+ create(context) {
+ const settings = getSettingsFromContext(context);
+ const useContextAlias = new Set();
+
+ if (!context.sourceCode.text.includes("useContext")) {
+ return {};
+ }
+ if (compare(settings.version, "19.0.0", "<")) {
+ return {};
+ }
+ return {
+ CallExpression(node) {
+ if (!isReactHookCall(node)) {
+ return;
+ }
+ if (!isReactHookCallWithNameAlias("useContext", context, [...useContextAlias])(node)) {
+ return;
+ }
+ context.report({
+ messageId: "noUseContext",
+ node,
+ fix(fixer) {
+ switch (node.callee.type) {
+ case T.Identifier:
+ return fixer.replaceText(node.callee, "use");
+ case T.MemberExpression:
+ return fixer.replaceText(node.callee.property, "use");
+ }
+ return null;
+ },
+ });
+ },
+ ImportDeclaration(node) {
+ if (node.source.value !== settings.importSource) {
+ return;
+ }
+ const isUseImported = node.specifiers
+ .some(isMatching({ local: { type: T.Identifier, name: "use" } }));
+ for (const specifier of node.specifiers) {
+ if (specifier.type !== T.ImportSpecifier) continue;
+ if (specifier.imported.type !== T.Identifier) continue;
+ if (specifier.imported.name === "useContext") {
+ if (specifier.local.name !== "useContext") {
+ useContextAlias.add(specifier.local.name);
+ context.report({
+ messageId: "noUseContext",
+ node: specifier,
+ });
+ return;
+ }
+ context.report({
+ messageId: "noUseContext",
+ node: specifier,
+ fix(fixer) {
+ if (isUseImported) {
+ return fixer.replaceText(specifier, " ".repeat(specifier.range[1] - specifier.range[0]));
+ }
+ return fixer.replaceText(specifier.imported, "use");
+ },
+ });
+ }
+ }
+ },
+ };
+ },
+ defaultOptions: [],
+});
diff --git a/packages/plugins/eslint-plugin/src/configs/all.ts b/packages/plugins/eslint-plugin/src/configs/all.ts
index 250115a05..2d7ccc549 100644
--- a/packages/plugins/eslint-plugin/src/configs/all.ts
+++ b/packages/plugins/eslint-plugin/src/configs/all.ts
@@ -50,6 +50,7 @@ export const rules = {
"@eslint-react/no-unstable-default-props": "warn",
"@eslint-react/no-unused-class-component-members": "warn",
"@eslint-react/no-unused-state": "warn",
+ "@eslint-react/no-use-context": "warn",
"@eslint-react/no-useless-fragment": "warn",
"@eslint-react/prefer-destructuring-assignment": "warn",
"@eslint-react/prefer-shorthand-boolean": "warn",
diff --git a/packages/plugins/eslint-plugin/src/configs/core.ts b/packages/plugins/eslint-plugin/src/configs/core.ts
index 9f910e69c..ebc9c1b27 100644
--- a/packages/plugins/eslint-plugin/src/configs/core.ts
+++ b/packages/plugins/eslint-plugin/src/configs/core.ts
@@ -41,6 +41,7 @@ export const rules = {
"@eslint-react/no-unstable-default-props": "warn",
"@eslint-react/no-unused-class-component-members": "warn",
"@eslint-react/no-unused-state": "warn",
+ "@eslint-react/no-use-context": "warn",
"@eslint-react/use-jsx-vars": "warn",
} as const satisfies RulePreset;
diff --git a/website/content/docs/rules/meta.json b/website/content/docs/rules/meta.json
index 16698ca27..626379f36 100644
--- a/website/content/docs/rules/meta.json
+++ b/website/content/docs/rules/meta.json
@@ -43,6 +43,7 @@
"no-unstable-default-props",
"no-unused-class-component-members",
"no-unused-state",
+ "no-use-context",
"no-useless-fragment",
"prefer-destructuring-assignment",
"prefer-react-namespace-import",
diff --git a/website/content/docs/rules/no-use-context.md b/website/content/docs/rules/no-use-context.md
new file mode 100644
index 000000000..7bffdacb3
--- /dev/null
+++ b/website/content/docs/rules/no-use-context.md
@@ -0,0 +1,68 @@
+---
+title: no-use-context
+---
+
+**Full Name in `eslint-plugin-react-x`**
+
+```plain copy
+react-x/no-use-context
+```
+
+**Full Name in `@eslint-react/eslint-plugin`**
+
+```plain copy
+@eslint-react/no-use-context
+```
+
+**Features**
+
+`🔍` `🔄`
+
+**Presets**
+
+- `core`
+- `recommended`
+- `recommended-typescript`
+- `recommended-type-checked`
+
+## What it does
+
+Disallows using `React.useContext`.
+
+In React 19, `use` is preferred over `useContext` because it is more flexible.
+
+An **unsafe** codemod is available for this rule.
+
+## Examples
+
+### Failing
+
+```tsx
+import { useContext } from "react";
+
+function Button() {
+ const theme = useContext(ThemeContext);
+ // ...
+}
+```
+
+### Passing
+
+```tsx
+import { use } from "react";
+
+function Button() {
+ const theme = use(ThemeContext);
+ // ...
+}
+```
+
+## Implementation
+
+- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.ts)
+- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.spec.ts)
+
+## Further Reading
+
+- [React Blog: New feature use](https://react.dev/blog/2024/12/05/react-19#new-feature-use)
+- [React: Reading context with use](https://react.dev/reference/react/use#reading-context-with-use)
diff --git a/website/content/docs/rules/overview.md b/website/content/docs/rules/overview.md
index 79315276d..c0e028051 100644
--- a/website/content/docs/rules/overview.md
+++ b/website/content/docs/rules/overview.md
@@ -62,6 +62,7 @@ full: true
| [`no-unstable-default-props`](no-unstable-default-props) | 1️⃣ | `🔍` | Prevents using referential-type values as default props in object destructuring. | |
| [`no-unused-class-component-members`](no-unused-class-component-members) | 0️⃣ | `🔍` | Warns unused class component methods and properties. | |
| [`no-unused-state`](no-unused-state) | 1️⃣ | `🔍` | Warns unused class component state. | |
+| [`no-use-context`](no-use-context) | 1️⃣ | `🔍` `🔄` | Prevents using `useContext` in favor of `use`. | >=19.0.0 |
| [`no-useless-fragment`](no-useless-fragment) | 1️⃣ | `🔍` `🔧` `⚙️` | Prevents using useless `fragment` components or `<>` syntax. | |
| [`prefer-destructuring-assignment`](prefer-destructuring-assignment) | 0️⃣ | `🔍` | Enforces using destructuring assignment over property assignment. | |
| [`prefer-react-namespace-import`](prefer-react-namespace-import) | 0️⃣ | `🔍` `🔧` | Enforces React is imported via a namespace import | |