Package Exports
- eslint-plugin-react-you-might-not-need-an-effect
Readme
ESLint - React - You Might Not Need An Effect
ESLint plugin to catch unnecessary React useEffects to make your code easier to follow, faster to run, and less error-prone. Highly recommended for new React developers as you learn its mental model, and even experienced developers may be surprised.
📦 Installation
NPM
npm install --save-dev eslint-plugin-react-you-might-not-need-an-effectYarn
yarn add -D eslint-plugin-react-you-might-not-need-an-effect⚙️ Configuration
Recommended
Add the plugin's recommended config to your ESLint configuration file to enable every rule as a warning.
Legacy config (.eslintrc)
{
"extends": [
"plugin:react-you-might-not-need-an-effect/legacy-recommended"
],
}Flat config (eslint.config.js)
import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";
export default [
reactYouMightNotNeedAnEffect.configs.recommended
];Custom
If not using the recommended config, manually set your languageOptions:
import globals from "globals";
// ...
{
globals: {
...globals.browser,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
};Suggested
The plugin can provide more accurate analysis when you pass the correct dependencies to your effects — consider using react-hooks/exhaustive-deps.
🔎 Rules
See the tests for extensive (in)valid examples for each rule.
no-derived-state — docs
Disallow storing derived state in an effect:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
}no-chain-state-updates — docs
Disallow chaining state updates in an effect:
function Game() {
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (round > 10) {
setIsGameOver(true);
}
}, [round]);
}no-event-handler — docs
Disallow using state and an effect as an event handler:
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the shopping cart!`);
}
}, [product]);
}no-adjust-state-on-prop-change — docs
Disallow adjusting state in an effect when a prop changes:
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
useEffect(() => {
setSelection(null);
}, [items]);
}no-reset-all-state-on-prop-change — docs
Disallow resetting all state in an effect when a prop changes:
function List({ items }) {
const [selection, setSelection] = useState(null);
useEffect(() => {
setSelection(null);
}, [items]);
}no-pass-live-state-to-parent — docs
Disallow passing live state to parents in an effect:
function Child({ onTextChanged }) {
const [text, setText] = useState();
useEffect(() => {
onTextChanged(text);
}, [onTextChanged, text]);
}no-pass-data-to-parent — docs
Disallow passing data to parents in an effect:
function Child({ onDataFetched }) {
const { data } = useQuery('/data')
useEffect(() => {
onDataFetched(data)
}, [data, onDataFetched]);
}no-pass-ref-to-parent — docs
Disallow passing refs, or data from callbacks registered on them, to parents in an effect. Use forwardRef instead:
function Child({ onRef }) {
const ref = useRef();
useEffect(() => {
onRef(ref.current);
}, [onRef, ref.current]);
}const Child = ({ onClicked }) => {
const ref = useRef();
useEffect(() => {
ref.current.addEventListener('click', (event) => {
onClicked(event);
});
}, [onClicked]);
}no-initialize-state
Disallow initializing state in an effect:
function Component() {
const [state, setState] = useState();
useEffect(() => {
setState("Hello World");
}, []);
}no-manage-parent
Disallow effects that only use props:
function Child({ isOpen, onClose }) {
useEffect(() => {
if (!isOpen) {
onClose();
}
}, [isOpen, onClose]);
}no-empty-effect
Disallow empty effects:
function Component() {
useEffect(() => {}, []);
}💬 Feedback
The ways to (mis)use an effect in real-world code are practically endless! This plugin is not exhaustive, and minimizes false positives at the expense of occasional false negatives. If you encounter unexpected behavior or see opportunities for improvement, please open an issue. Your feedback helps improve the plugin for everyone!