Skip to content

no-useless-use-effect

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.

OptionDefault
environment"roblox-ts"
hooks["useEffect", "useLayoutEffect", "useInsertionEffect"]
propertyCallbackPrefixes["on"]
refHooks["useRef"]
stateHooks["useState", "useReducer"]
reportAdjustStatetrue
reportDerivedStatetrue
reportDuplicateDepstrue
reportEffectChaintrue
reportEmptyEffecttrue
reportEventFlagtrue
reportEventSpecificLogictrue
reportExternalStoretrue
reportInitializeStatetrue
reportLogOnlytrue
reportMixedDerivedStatetrue
reportNotifyParenttrue
reportPassRefToParenttrue
reportResetStatetrue
eslint.config.js
import ceaseNonsense from "eslint-plugin-cease-nonsense";
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 }} />;
}