Skip to content

no-useless-use-effect

errorSuggestion

Reports effect patterns that only move data around inside React instead of doing real side effects.

This rule looks for useEffect, useLayoutEffect, and useInsertionEffect by default. It reports effects that are better handled during render, in event handlers, or with a different hook.

The built-in reports are:

  • adjustState, state is adjusted from prop changes
  • derivedState, state is only derived from props or other state
  • duplicateDeps, multiple effects use the same dependency list
  • effectChain, one effect depends on state written by another effect chain
  • emptyEffect, the callback body is empty
  • eventFlag, an event is routed through a boolean flag in state
  • eventSpecificLogic, event logic runs later in an effect based on state
  • externalStore, an external subscription syncs into state, use useSyncExternalStore instead
  • initializeState, state is initialized in an effect with a constant value
  • logOnly, the effect only calls console.log
  • mixedDerivedState, derived setter calls are mixed with other non-side-effect work
  • notifyParent, the effect only calls a parent callback
  • passRefToParent, the effect passes a ref to a parent callback
  • resetState, state is reset from prop changes instead of using a key

Async effect callbacks are ignored.

Option Default
environment "roblox-ts"
hooks ["useEffect", "useLayoutEffect", "useInsertionEffect"]
propertyCallbackPrefixes ["on"]
refHooks ["useRef"]
stateHooks ["useState", "useReducer"]
reportAdjustState true
reportDerivedState true
reportDuplicateDeps true
reportEffectChain true
reportEmptyEffect true
reportEventFlag true
reportEventSpecificLogic true
reportExternalStore true
reportInitializeState true
reportLogOnly true
reportMixedDerivedState true
reportNotifyParent true
reportPassRefToParent true
reportResetState true
eslint.config.js
import ceaseNonsense from "@pobammer-ts/eslint-cease-nonsense-rules";
export default [
{
plugins: {
"cease-nonsense": ceaseNonsense,
},
rules: {
"cease-nonsense/no-useless-use-effect": ["error", {
environment: "roblox-ts",
hooks: ["useEffect", "useLayoutEffect", "useInsertionEffect"],
propertyCallbackPrefixes: ["on"],
reportDerivedState: true,
reportNotifyParent: true,
reportEventFlag: true,
}],
},
},
];
Incorrect
import { useEffect, useState } from "@rbxts/react";
function Profile(properties) {
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${properties.first} ${properties.last}`);
}, [properties.first, properties.last]);
}
function Form(properties) {
useEffect(() => {
properties.onChange(properties.value);
}, [properties.value, properties.onChange]);
}
function SubmitButton() {
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
if (!submitted) return;
submitForm();
setSubmitted(false);
}, [submitted]);
}
Correct
function Profile(properties) {
const fullName = `${properties.first} ${properties.last}`;
return <textlabel Text={fullName} />;
}
function Form(properties) {
function handleSubmit() {
properties.onChange(properties.value);
}
return <textbutton Event={{ Activated: handleSubmit }} />;
}
function SubmitButton() {
function handleSubmit() {
submitForm();
}
return <textbutton Event={{ Activated: handleSubmit }} />;
}