bun/react-prefer-updater-function
Prefer setItems(previous => ..) to a new array or object made from the state of the last render
The items in const [items, setItems] = useState([]) is the state as of the last render. If setItems([...items, item]) runs after an await, in a timer, or twice before the next render, items is out of date and the other update is lost. A function gets the current state: setItems(previous => [...previous, item]).
The rule reports a setter that's called with an array or object literal that spreads its own state.
Configure#
Off by default. To turn it on:
bunfig.toml
[lint.rules]
"bun/react-prefer-updater-function" = "error" # or "warn"Examples#
example.jsx1 problem
import { useState } from "react";
function Cart() {
const [items, setItems] = useState([]);
async function add(id) {
const item = await loadItem(id);
setItems([...itemsitems may be out of date when setItems is called. Pass a function: setItems(previous => ..)., item]);
}
// ...
}example.jsx1 problem
import { useState } from "react";
function Profile() {
const [form, setForm] = useState({ name: "", email: "" });
return <input value={form.name} onChange={event => setForm({ ...formform may be out of date when setForm is called. Pass a function: setForm(previous => ..)., name: event.target.value })} />;
}example.jsxNo problems
import { useState } from "react";
function Cart() {
const [items, setItems] = useState([]);
async function add(id) {
const item = await loadItem(id);
setItems(previous => [...previous, item]);
}
// ...
}If neither name comes from a useState() in the file, for example because both are props, the rule goes by the names. setRows is treated as the setter for rows:
example.jsx1 problem
function AddRow({ rows, setRows }) {
return <button onClick={() => setRows([...rowsrows may be out of date when setRows is called. Pass a function: setRows(previous => ..)., { id: rows.length }])}>Add</button>;
}Spreading something else is fine:
example.jsxNo problems
import { useState } from "react";
function Search({ defaults }) {
const [filters, setFilters] = useState({});
return <button onClick={() => setFilters({ ...defaults })}>Reset</button>;
}