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
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,15 @@ export const EnvironmentConfigSchema = z.object({

/**
* Validate that dependencies supplied to effect hooks are exhaustive.
* Can be:
* - 'off': No validation (default)
* - 'all': Validate and report both missing and extra dependencies
* - 'missing-only': Only report missing dependencies
* - 'extra-only': Only report extra/unnecessary dependencies
*/
validateExhaustiveEffectDependencies: z.boolean().default(false),
validateExhaustiveEffectDependencies: z
.enum(['off', 'all', 'missing-only', 'extra-only'])
.default('off'),

/**
* When this is true, rather than pruning existing manual memoization but ensuring or validating
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export function validateExhaustiveDependencies(
reactive,
startMemo.depsLoc,
ErrorCategory.MemoDependencies,
'all',
);
if (diagnostic != null) {
error.pushDiagnostic(diagnostic);
Expand All @@ -159,7 +160,7 @@ export function validateExhaustiveDependencies(
onStartMemoize,
onFinishMemoize,
onEffect: (inferred, manual, manualMemoLoc) => {
if (env.config.validateExhaustiveEffectDependencies === false) {
if (env.config.validateExhaustiveEffectDependencies === 'off') {
return;
}
if (DEBUG) {
Expand Down Expand Up @@ -195,12 +196,17 @@ export function validateExhaustiveDependencies(
});
}
}
const effectReportMode =
typeof env.config.validateExhaustiveEffectDependencies === 'string'
? env.config.validateExhaustiveEffectDependencies
: 'all';
const diagnostic = validateDependencies(
Array.from(inferred),
manualDeps,
reactive,
manualMemoLoc,
ErrorCategory.EffectExhaustiveDependencies,
effectReportMode,
);
if (diagnostic != null) {
error.pushDiagnostic(diagnostic);
Expand All @@ -220,6 +226,7 @@ function validateDependencies(
category:
| ErrorCategory.MemoDependencies
| ErrorCategory.EffectExhaustiveDependencies,
exhaustiveDepsReportMode: 'all' | 'missing-only' | 'extra-only',
): CompilerDiagnostic | null {
// Sort dependencies by name and path, with shorter/non-optional paths first
inferred.sort((a, b) => {
Expand Down Expand Up @@ -370,9 +377,20 @@ function validateDependencies(
extra.push(dep);
}

if (missing.length !== 0 || extra.length !== 0) {
// Filter based on report mode
const filteredMissing =
exhaustiveDepsReportMode === 'extra-only' ? [] : missing;
const filteredExtra =
exhaustiveDepsReportMode === 'missing-only' ? [] : extra;

if (filteredMissing.length !== 0 || filteredExtra.length !== 0) {
let suggestion: CompilerSuggestion | null = null;
if (manualMemoLoc != null && typeof manualMemoLoc !== 'symbol') {
if (
manualMemoLoc != null &&
typeof manualMemoLoc !== 'symbol' &&
manualMemoLoc.start.index != null &&
manualMemoLoc.end.index != null
) {
suggestion = {
description: 'Update dependencies',
range: [manualMemoLoc.start.index, manualMemoLoc.end.index],
Expand All @@ -388,8 +406,13 @@ function validateDependencies(
.join(', ')}]`,
};
}
const diagnostic = createDiagnostic(category, missing, extra, suggestion);
for (const dep of missing) {
const diagnostic = createDiagnostic(
category,
filteredMissing,
filteredExtra,
suggestion,
);
for (const dep of filteredMissing) {
let reactiveStableValueHint = '';
if (isStableType(dep.identifier)) {
reactiveStableValueHint =
Expand All @@ -402,7 +425,7 @@ function validateDependencies(
loc: dep.loc,
});
}
for (const dep of extra) {
for (const dep of filteredExtra) {
if (dep.root.kind === 'Global') {
diagnostic.withDetails({
kind: 'error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @validateExhaustiveEffectDependencies
// @validateExhaustiveEffectDependencies:"all"
import {useEffect, useEffectEvent} from 'react';

function Component({x, y, z}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @validateExhaustiveEffectDependencies
// @validateExhaustiveEffectDependencies:"all"
import {useEffect, useEffectEvent} from 'react';

function Component({x, y, z}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

## Input

```javascript
// @validateExhaustiveEffectDependencies:"extra-only"
import {useEffect} from 'react';

function Component({x, y, z}) {
// no error: missing dep not reported in extra-only mode
useEffect(() => {
log(x);
}, []);

// error: extra dep - y
useEffect(() => {
log(x);
}, [x, y]);

// error: extra dep - y (missing dep - z not reported)
useEffect(() => {
log(x, z);
}, [x, y]);

// error: extra dep - x.y
useEffect(() => {
log(x);
}, [x.y]);
}

```


## Error

```
Found 3 errors:

Error: Found extra effect dependencies

Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.

error.invalid-exhaustive-effect-deps-extra-only.ts:13:9
11 | useEffect(() => {
12 | log(x);
> 13 | }, [x, y]);
| ^ Unnecessary dependency `y`
14 |
15 | // error: extra dep - y (missing dep - z not reported)
16 | useEffect(() => {

Inferred dependencies: `[x]`

Error: Found extra effect dependencies

Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.

error.invalid-exhaustive-effect-deps-extra-only.ts:18:9
16 | useEffect(() => {
17 | log(x, z);
> 18 | }, [x, y]);
| ^ Unnecessary dependency `y`
19 |
20 | // error: extra dep - x.y
21 | useEffect(() => {

Inferred dependencies: `[x, z]`

Error: Found extra effect dependencies

Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.

error.invalid-exhaustive-effect-deps-extra-only.ts:23:6
21 | useEffect(() => {
22 | log(x);
> 23 | }, [x.y]);
| ^^^ Overly precise dependency `x.y`, use `x` instead
24 | }
25 |

Inferred dependencies: `[x]`
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @validateExhaustiveEffectDependencies:"extra-only"
import {useEffect} from 'react';

function Component({x, y, z}) {
// no error: missing dep not reported in extra-only mode
useEffect(() => {
log(x);
}, []);

// error: extra dep - y
useEffect(() => {
log(x);
}, [x, y]);

// error: extra dep - y (missing dep - z not reported)
useEffect(() => {
log(x, z);
}, [x, y]);

// error: extra dep - x.y
useEffect(() => {
log(x);
}, [x.y]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

## Input

```javascript
// @validateExhaustiveEffectDependencies:"missing-only"
import {useEffect} from 'react';

function Component({x, y, z}) {
// error: missing dep - x
useEffect(() => {
log(x);
}, []);

// no error: extra dep not reported in missing-only mode
useEffect(() => {
log(x);
}, [x, y]);

// error: missing dep - z (extra dep - y not reported)
useEffect(() => {
log(x, z);
}, [x, y]);

// error: missing dep x
useEffect(() => {
log(x);
}, [x.y]);
}

```


## Error

```
Found 3 errors:

Error: Found missing effect dependencies

Missing dependencies can cause an effect to fire less often than it should.

error.invalid-exhaustive-effect-deps-missing-only.ts:7:8
5 | // error: missing dep - x
6 | useEffect(() => {
> 7 | log(x);
| ^ Missing dependency `x`
8 | }, []);
9 |
10 | // no error: extra dep not reported in missing-only mode

Inferred dependencies: `[x]`

Error: Found missing effect dependencies

Missing dependencies can cause an effect to fire less often than it should.

error.invalid-exhaustive-effect-deps-missing-only.ts:17:11
15 | // error: missing dep - z (extra dep - y not reported)
16 | useEffect(() => {
> 17 | log(x, z);
| ^ Missing dependency `z`
18 | }, [x, y]);
19 |
20 | // error: missing dep x

Inferred dependencies: `[x, z]`

Error: Found missing effect dependencies

Missing dependencies can cause an effect to fire less often than it should.

error.invalid-exhaustive-effect-deps-missing-only.ts:22:8
20 | // error: missing dep x
21 | useEffect(() => {
> 22 | log(x);
| ^ Missing dependency `x`
23 | }, [x.y]);
24 | }
25 |

Inferred dependencies: `[x]`
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @validateExhaustiveEffectDependencies:"missing-only"
import {useEffect} from 'react';

function Component({x, y, z}) {
// error: missing dep - x
useEffect(() => {
log(x);
}, []);

// no error: extra dep not reported in missing-only mode
useEffect(() => {
log(x);
}, [x, y]);

// error: missing dep - z (extra dep - y not reported)
useEffect(() => {
log(x, z);
}, [x, y]);

// error: missing dep x
useEffect(() => {
log(x);
}, [x.y]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @validateExhaustiveEffectDependencies
// @validateExhaustiveEffectDependencies:"all"
import {useEffect} from 'react';

function Component({x, y, z}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @validateExhaustiveEffectDependencies
// @validateExhaustiveEffectDependencies:"all"
import {useEffect} from 'react';

function Component({x, y, z}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies:"all"
import {
useCallback,
useTransition,
Expand Down Expand Up @@ -69,7 +69,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies:"all"
import {
useCallback,
useTransition,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies:"all"
import {
useCallback,
useTransition,
Expand Down
Loading
Loading