eslint-plugin-no-object-deps
v1.0.0
Published
ESLint plugin to disallow using objects/arrays in React useEffect dependencies
Downloads
87
Maintainers
Readme
eslint-plugin-no-object-deps
An ESLint plugin that prevents the use of non-primitive values (objects, arrays, functions) in React useEffect dependency arrays. This helps avoid unnecessary re-renders and improves React application performance.
Why Use This Plugin?
React's useEffect hook compares dependencies using Object.is() (shallow comparison). When you pass objects, arrays, or functions as dependencies, they are recreated on every render, causing the effect to run unnecessarily. This can lead to:
- Performance issues due to excessive re-renders
- Infinite loops in effect chains
- Unexpected behavior in your React components
Installation
npm install --save-dev eslint-plugin-no-object-depsor with yarn:
yarn add --dev eslint-plugin-no-object-depsUsage
Basic Configuration
Add the plugin to your ESLint configuration:
.eslintrc.js
module.exports = {
plugins: ['no-object-deps'],
rules: {
'no-object-deps/no-object-deps': 'error'
}
};Using Recommended Configuration
module.exports = {
extends: ['plugin:no-object-deps/recommended']
};TypeScript Projects
Make sure you have the TypeScript ESLint parser configured:
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json'
},
plugins: ['no-object-deps'],
rules: {
'no-object-deps/no-object-deps': 'error'
}
};Rule Details
This rule disallows non-primitive values in React useEffect dependency arrays.
❌ Invalid Code
import { useEffect } from 'react';
function Component() {
const obj = { key: 'value' };
const arr = [1, 2, 3];
const fn = () => console.log('hello');
// ❌ Objects not allowed
useEffect(() => {
console.log('effect');
}, [obj]);
// ❌ Arrays not allowed
useEffect(() => {
console.log('effect');
}, [arr]);
// ❌ Functions not allowed
useEffect(() => {
console.log('effect');
}, [fn]);
// ❌ Multiple non-primitives
useEffect(() => {
console.log('effect');
}, [obj, arr, fn]);
}✅ Valid Code
import { useEffect } from 'react';
function Component() {
const count = 5;
const name = "test";
const isActive = true;
const nullValue = null;
const undefinedValue = undefined;
// ✅ Primitives are allowed
useEffect(() => {
console.log('effect');
}, [count, name, isActive, nullValue, undefinedValue]);
// ✅ Empty dependency array is allowed
useEffect(() => {
console.log('effect');
}, []);
// ✅ No dependency array is allowed
useEffect(() => {
console.log('effect');
});
// ✅ Union types with primitives are allowed
const value: string | number = 'test';
useEffect(() => {
console.log('effect');
}, [value]);
}Configuration Options
The rule accepts an options object with the following properties:
allowedNonPrimitives
An array of type names that should be allowed despite being non-primitive.
{
"no-object-deps/no-object-deps": ["error", {
"allowedNonPrimitives": ["MySpecialType", "ConfigObject"]
}]
}Common Patterns and Solutions
1. Object Dependencies
❌ Problem:
const config = { apiUrl: 'https://api.example.com' };
useEffect(() => {
fetchData(config);
}, [config]); // config is recreated every render✅ Solution:
// Option 1: Move object outside component
const CONFIG = { apiUrl: 'https://api.example.com' };
function Component() {
useEffect(() => {
fetchData(CONFIG);
}, []); // No dependencies needed
}
// Option 2: Use individual primitive values
function Component() {
const apiUrl = 'https://api.example.com';
useEffect(() => {
fetchData({ apiUrl });
}, [apiUrl]); // Only primitive dependency
}
// Option 3: Use useMemo for complex objects
function Component() {
const config = useMemo(() => ({
apiUrl: 'https://api.example.com'
}), []); // Memoized with empty deps
useEffect(() => {
fetchData(config);
}, [config]);
}2. Array Dependencies
❌ Problem:
const ids = [1, 2, 3];
useEffect(() => {
fetchItems(ids);
}, [ids]); // Array is recreated every render✅ Solution:
// Option 1: Convert to string for comparison
const ids = [1, 2, 3];
const idsString = ids.join(',');
useEffect(() => {
fetchItems(ids);
}, [idsString]); // String is primitive
// Option 2: Use useMemo
const ids = useMemo(() => [1, 2, 3], []);
useEffect(() => {
fetchItems(ids);
}, [ids]);3. Function Dependencies
❌ Problem:
const handleData = (data) => console.log(data);
useEffect(() => {
api.onData(handleData);
}, [handleData]); // Function is recreated every render✅ Solution:
// Option 1: Use useCallback
const handleData = useCallback((data) => {
console.log(data);
}, []);
useEffect(() => {
api.onData(handleData);
}, [handleData]);
// Option 2: Move function outside component
function handleData(data) {
console.log(data);
}
function Component() {
useEffect(() => {
api.onData(handleData);
}, []); // No dependencies needed
}Supported React Patterns
This rule supports both direct and namespaced useEffect calls:
// Direct import
import { useEffect } from 'react';
useEffect(() => {}, [dependency]);
// Namespace import
import React from 'react';
React.useEffect(() => {}, [dependency]);Requirements
- Node.js 16+
- ESLint 8+
- TypeScript 4.7+ (for TypeScript projects)
- @typescript-eslint/parser 6+ (for TypeScript projects)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Setup
- Clone the repository
- Install dependencies:
npm install - Build the project:
npm run build - Run tests:
npm test
Running Tests
npm test # Run tests once
npm run test:watch # Run tests in watch modeLicense
MIT
Related Rules
- react-hooks/exhaustive-deps - Ensures effects declare all dependencies
- @typescript-eslint/no-misused-promises - Prevents common Promise misuse patterns
Changelog
1.0.0
- Initial release
- Support for detecting objects, arrays, and functions in useEffect dependencies
- TypeScript support with advanced type checking
- Configurable allowed non-primitives
- Support for both direct and namespaced React imports
