avoidDerivedStateAvoid storing derived state. Instead, compute "{{state}}" directly during render.small-rules/no-derived-stateDisallow storing derived state in an effect.
Ported from
eslint-plugin-react-you-might-not-need-an-effect/no-derived-state.
The upstream rule points at the “Updating state based on props or
state” section
of the React docs, so that’s where the intent comes from.
This catches state that’s just a copy of something you could compute: an effect sets state whose value comes from props or other state. The copy only refreshes when the effect runs, and the effect only runs after render, so you’re permanently one render behind whatever it’s derived from.
Just compute it during render. If the computation is expensive, memoize it, but the common case on Roblox is a cheap calculation and the memo is the overhead. Killing the state copy also kills the whole class of “why is my label stale” bugs.
avoidDerivedStateAvoid storing derived state. Instead, compute "{{state}}" directly during render.This rule accepts one options object after the severity.
import React, { useEffect, useState } from "@rbxts/react";
export function Form(): React.Element { const [firstName, setFirstName] = useState("Taylor"); const [lastName, setLastName] = useState("Swift"); const [fullName, setFullName] = useState("");
useEffect(() => { setFullName(`${firstName} ${lastName}`); }, [firstName, lastName]);
return ( <frame> <textlabel Text={fullName} /> <textbox Text={firstName} TextChanged={(textbox: { readonly Text: string }) => setFirstName(textbox.Text)} /> <textbox Text={lastName} TextChanged={(textbox: { readonly Text: string }) => setLastName(textbox.Text)} /> </frame> );}import React, { useState } from "@rbxts/react";
export function Form(): React.Element { // oxlint-disable-next-line no-unused-vars, sonar/no-unused-vars, small-rules/no-dead-store -- The setter stays unused; the sample only derives the full name. const [firstName, setFirstName] = useState("Taylor"); // oxlint-disable-next-line no-unused-vars, sonar/no-unused-vars, small-rules/no-dead-store -- The setter stays unused; the sample only derives the full name. const [lastName, setLastName] = useState("Swift");
const fullName = `${firstName} ${lastName}`;
return <textlabel Text={fullName} />;}