Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/// <reference types="next" />
/// <reference types="next/image-types/global" />

Expand Down
37 changes: 37 additions & 0 deletions src/content/reference/eslint-plugin-react-hooks/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: eslint-plugin-react-hooks
---

<Intro>

`eslint-plugin-react-hooks` provides ESLint rules to enforce the [Rules of React](/reference/rules).

</Intro>

This plugin helps you catch violations of React's rules at build time, ensuring your components and hooks follow React's rules for correctness and performance. The lints cover both fundamental React patterns (exhaustive-deps and rules-of-hooks) and issues flagged by React Compiler. React Compiler diagnostics are automatically surfaced by this ESLint plugin, and can be used even if your app hasn't adopted the compiler yet.

<Note>
When the compiler reports a diagnostic, it means that the compiler was able to statically detect a pattern that is not supported or breaks the Rules of React. When it detects this, it **automatically** skips over those components and hooks, while keeping the rest of your app compiled. This ensures optimal coverage of safe optimizations that won't break your app.

What this means for linting, is that you don’t need to fix all violations immediately. Address them at your own pace to gradually increase the number of optimized components.
</Note>

## Available Lints {/*available-lints*/}

* [`component-hook-factories`](/reference/eslint-plugin-react-hooks/lints/component-hook-factories) - Validates against higher order functions defining nested components or hooks
* [`config`](/reference/eslint-plugin-react-hooks/lints/config) - Validates the compiler configuration options
* [`error-boundaries`](/reference/eslint-plugin-react-hooks/lints/error-boundaries) - Validates usage of Error Boundaries instead of try/catch for child errors
* [`exhaustive-deps`](/reference/eslint-plugin-react-hooks/lints/exhaustive-deps) - Validates that dependency arrays for React hooks contain all necessary dependencies
* [`gating`](/reference/eslint-plugin-react-hooks/lints/gating) - Validates configuration of gating mode
* [`globals`](/reference/eslint-plugin-react-hooks/lints/globals) - Validates against assignment/mutation of globals during render
* [`immutability`](/reference/eslint-plugin-react-hooks/lints/immutability) - Validates against mutating props, state, and other immutable values
* [`incompatible-library`](/reference/eslint-plugin-react-hooks/lints/incompatible-library) - Validates against usage of libraries which are incompatible with memoization
* [`preserve-manual-memoization`](/reference/eslint-plugin-react-hooks/lints/preserve-manual-memoization) - Validates that existing manual memoization is preserved by the compiler
* [`purity`](/reference/eslint-plugin-react-hooks/lints/purity) - Validates that components/hooks are pure by checking known-impure functions
* [`refs`](/reference/eslint-plugin-react-hooks/lints/refs) - Validates correct usage of refs, not reading/writing during render
* [`rules-of-hooks`](/reference/eslint-plugin-react-hooks/lints/rules-of-hooks) - Validates that components and hooks follow the Rules of Hooks
* [`set-state-in-effect`](/reference/eslint-plugin-react-hooks/lints/set-state-in-effect) - Validates against calling setState synchronously in an effect
* [`set-state-in-render`](/reference/eslint-plugin-react-hooks/lints/set-state-in-render) - Validates against setting state during render
* [`static-components`](/reference/eslint-plugin-react-hooks/lints/static-components) - Validates that components are static, not recreated every render
* [`unsupported-syntax`](/reference/eslint-plugin-react-hooks/lints/unsupported-syntax) - Validates against syntax that React Compiler does not support
* [`use-memo`](/reference/eslint-plugin-react-hooks/lints/use-memo) - Validates usage of the `useMemo` hook without a return value
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: component-hook-factories
---

<Intro>

Validates against higher order functions defining nested components or hooks. Components and hooks should be defined at the module level.

</Intro>

## Rule Details {/*rule-details*/}

Defining components or hooks inside other functions creates new instances on every call. React treats each as a completely different component, destroying and recreating the entire component tree, losing all state, and causing performance problems.

### Invalid {/*invalid*/}

Examples of incorrect code for this rule:

```js {expectedErrors: {'react-compiler': [14]}}
// ❌ Factory function creating components
function createComponent(defaultValue) {
return function Component() {
// ...
};
}

// ❌ Component defined inside component
function Parent() {
function Child() {
// ...
}

return <Child />;
}

// ❌ Hook factory function
function createCustomHook(endpoint) {
return function useData() {
// ...
};
}
```

### Valid {/*valid*/}

Examples of correct code for this rule:

```js
// ✅ Component defined at module level
function Component({ defaultValue }) {
// ...
}

// ✅ Custom hook at module level
function useData(endpoint) {
// ...
}
```

## Troubleshooting {/*troubleshooting*/}

### I need dynamic component behavior {/*dynamic-behavior*/}

You might think you need a factory to create customized components:

```js
// ❌ Wrong: Factory pattern
function makeButton(color) {
return function Button({children}) {
return (
<button style={{backgroundColor: color}}>
{children}
</button>
);
};
}

const RedButton = makeButton('red');
const BlueButton = makeButton('blue');
```

Pass [JSX as children](/learn/passing-props-to-a-component#passing-jsx-as-children) instead:

```js
// ✅ Better: Pass JSX as children
function Button({color, children}) {
return (
<button style={{backgroundColor: color}}>
{children}
</button>
);
}

function App() {
return (
<>
<Button color="red">Red</Button>
<Button color="blue">Blue</Button>
</>
);
}
```
90 changes: 90 additions & 0 deletions src/content/reference/eslint-plugin-react-hooks/lints/config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: config
---

<Intro>

Validates the compiler [configuration options](/reference/react-compiler/configuration).

</Intro>

## Rule Details {/*rule-details*/}

React Compiler accepts various [configuration options](/reference/react-compiler/configuration) to control its behavior. This rule validates that your configuration uses correct option names and value types, preventing silent failures from typos or incorrect settings.

### Invalid {/*invalid*/}

Examples of incorrect code for this rule:

```js
// ❌ Unknown option name
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compileMode: 'all' // Typo: should be compilationMode
}]
]
};

// ❌ Invalid option value
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'everything' // Invalid: use 'all' or 'infer'
}]
]
};
```

### Valid {/*valid*/}

Examples of correct code for this rule:

```js
// ✅ Valid compiler configuration
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'infer',
panicThreshold: 'critical_errors'
}]
]
};
```

## Troubleshooting {/*troubleshooting*/}

### Configuration not working as expected {/*config-not-working*/}

Your compiler configuration might have typos or incorrect values:

```js
// ❌ Wrong: Common configuration mistakes
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
// Typo in option name
compilationMod: 'all',
// Wrong value type
panicThreshold: true,
// Unknown option
optimizationLevel: 'max'
}]
]
};
```

Check the [configuration documentation](/reference/react-compiler/configuration) for valid options:

```js
// ✅ Better: Valid configuration
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'all', // or 'infer'
panicThreshold: 'none', // or 'critical_errors', 'all_errors'
// Only use documented options
}]
]
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: error-boundaries
---

<Intro>

Validates usage of Error Boundaries instead of try/catch for errors in child components.

</Intro>

## Rule Details {/*rule-details*/}

Try/catch blocks can't catch errors that happen during React's rendering process. Errors thrown in rendering methods or hooks bubble up through the component tree. Only [Error Boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary) can catch these errors.

### Invalid {/*invalid*/}

Examples of incorrect code for this rule:

```js {expectedErrors: {'react-compiler': [4]}}
// ❌ Try/catch won't catch render errors
function Parent() {
try {
return <ChildComponent />; // If this throws, catch won't help
} catch (error) {
return <div>Error occurred</div>;
}
}
```

### Valid {/*valid*/}

Examples of correct code for this rule:

```js
// ✅ Using error boundary
function Parent() {
return (
<ErrorBoundary>
<ChildComponent />
</ErrorBoundary>
);
}
```

## Troubleshooting {/*troubleshooting*/}

### Why is the linter telling me not to wrap `use` in `try`/`catch`? {/*why-is-the-linter-telling-me-not-to-wrap-use-in-trycatch*/}

The `use` hook doesn't throw errors in the traditional sense, it suspends component execution. When `use` encounters a pending promise, it suspends the component and lets React show a fallback. Only Suspense and Error Boundaries can handle these cases. The linter warns against `try`/`catch` around `use` to prevent confusion as the `catch` block would never run.

```js {expectedErrors: {'react-compiler': [5]}}
// ❌ Try/catch around `use` hook
function Component({promise}) {
try {
const data = use(promise); // Won't catch - `use` suspends, not throws
return <div>{data}</div>;
} catch (error) {
return <div>Failed to load</div>; // Unreachable
}
}
Comment on lines +49 to +60
Copy link

@bthall16 bthall16 Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the intention is for the implementation details of use to be unnecessary for React developers, but in the current implementation of use it will throw and the catch block will run. Someone could log to the console before returning <div>Failed to load</div> and see the catch block did indeed run, couldn't they? While it totally makes sense for developers to not catch or otherwise manipulate what use throws, it seems like the docs stating use doesn't throw will cause confusion if and when they encounter it throwing.

EDIT: Maybe this is only relevant when using the compiler. I'm looking at it from the perspective of a non-compiled React component.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be deprecated soon: facebook/react#34032


// ✅ Error boundary catches `use` errors
function App() {
return (
<ErrorBoundary fallback={<div>Failed to load</div>}>
<Suspense fallback={<div>Loading...</div>}>
<DataComponent promise={fetchData()} />
</Suspense>
</ErrorBoundary>
);
}
```
Loading