Skip to content

No Adjust State on Prop Change

Suggestion
small-rules/no-adjust-state-on-prop-change

Disallow adjusting state in an effect when a prop changes.

Rationale

Ported from eslint-plugin-react-you-might-not-need-an-effect/no-adjust-state-on-prop-change. The upstream rule points at the “Adjusting some state when a prop changes” section of the React docs.

It flags an effect whose dependencies include a prop, and whose body synchronously writes state. It’s a prop-to-state sync. It costs two renders. The prop arrives and you render with the old state. Then the effect runs and you render again with the fixed one. During that gap, the UI shows state that’s already wrong. On Roblox that shows up as a textlabel or a frame popping in a frame late.

The fix is to not copy the prop into state at all. Compute what you need during render and let the prop stay the source of truth. If the state has its own lifecycle, there are better tools than an effect for resetting it. More often the state shouldn’t exist.

Diagnostic Messages

avoidAdjustingStateWhenAPropChanges
Avoid adjusting state when a prop changes. Instead, adjust "{{state}}" directly during render when {{props}} changes, or refactor your state to avoid this need entirely.

Configuration

This rule accepts one options object after the severity.

environmentOptional

The React environment: 'roblox-ts' uses @rbxts/react, 'standard' uses react.

{
"jsPlugins": [
"@pobammer-ts/small-rules"
],
"rules": {
"small-rules/no-adjust-state-on-prop-change": [
"error",
{
"environment": "roblox-ts"
}
]
}
}

Examples

State adjusted after a prop change
import React, { useEffect, useState } from "@rbxts/react";
export function List({ items }: { items: ReadonlyArray<string> }): React.Element {
const [selection, setSelection] = useState<string | undefined>();
useEffect(() => {
setSelection(undefined);
}, [items]);
return <textlabel Text={selection} />;
}
State adjusted during render
import React, { useState } from "@rbxts/react";
export function List({ items }: { items: ReadonlyArray<string> }): React.Element {
const [selection, setSelection] = useState<string | undefined>(undefined);
const [previousItems, setPreviousItems] = useState(items);
if (items !== previousItems) {
setPreviousItems(items);
setSelection(undefined);
}
return <textlabel Text={selection} />;
}