Skip to content

No Chain State Updates

Suggestion
small-rules/no-chain-state-updates

Disallow chaining state changes in an effect.

Rationale

Ported from eslint-plugin-react-you-might-not-need-an-effect/no-chain-state-updates. The upstream rule points at the “Chains of computations” section of the React docs, so that’s where the intent comes from.

This flags the shape where an effect depends on state and then writes different state. State A changes, the effect runs, it sets state B, you render again. That’s a chain, and chains are how one input change becomes four renders. If B happens to be in another effect’s dependencies, the chain keeps going.

The chain usually exists because someone wanted a computed value but reached for an effect instead of computing during render. Set both states where the change originates, or derive the follow-on value during render and skip the middleman. The rule can’t tell whether the chain is deliberate, say fetch-then-set, so treat it as a nudge to look rather than a verdict.

Diagnostic Messages

avoidChainingStateUpdates
Avoid chaining state changes. When possible, update "{{state}}" along with other relevant state simultaneously.

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-chain-state-updates": [
"error",
{
"environment": "roblox-ts"
}
]
}
}

Examples

State update chained through an effect
import React, { useEffect, useState } from "@rbxts/react";
export function Game(): React.Element {
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (round > 10) {
setIsGameOver(true);
}
}, [round]);
return (
<frame>
<textlabel Text={isGameOver} />
<textbutton Text={round} onActivated={() => setRound(round + 1)} />
</frame>
);
}
Related state updated together
import React, { useState } from "@rbxts/react";
export function Game(): React.Element {
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
function handleRoundComplete(): void {
const nextRound = round + 1;
setRound(nextRound);
if (nextRound > 10) {
setIsGameOver(true);
}
}
return (
<frame>
<textlabel Text={isGameOver} />
<textbutton Text={round} Event={{ Activated: handleRoundComplete }} />
</frame>
);
}