Diagnostics
LUM2001: Stale derived value
A value derived from a signal is computed once in setup and never updates.
Severity: warning · Category: reactivity (2xxx)
Reported by: the compiler. A warning. Does not apply to async components, whose body re-runs.
#What it means
A component's body runs once. const doubled = count() * 2 reads the signal at setup time and
stores a number, so JSX that uses doubled shows the first value forever.
The compiler treats a zero-argument call of a local or imported binding (count(),
user.name()) as a signal read, and so does a read of the component's props. Calls that can only
run on the server are not reads, so server pages can store request data in constants:
lumiose's request APIs:url(),params(),locals(),cookies(),request(),response()andpublicEnv(). Inside SPA routes,url()andparams()fromlumiose/routerare reactive and still count.lumiose's control-flow APIs, which never return:notFound(),redirect()anderror(), soconst project = getProject(slug) ?? notFound()is fine.- Anything imported from a server-only module (
*.server.ts,src/server/**), such asqueries().getBook(isbn). Data functions from a workspace package (getDashboard()from@acme/data) are not known to be server-only: re-export them from a module undersrc/server/(export { getDashboard } from "@acme/data";) and import them from there.
// src/routes/books/[isbn]/page.stsx (a server page): no warning
const user = locals().user ?? null;
const next = url().searchParams.get("next");
const featured = queries().featuredBooks(6);Pages and layouts whose route mode is static or server render once per request (or build), so
their own props are plain values too: deriving from them is fine. Routes in spa mode, islands
and other components re-render with new props and still get the warning. @lumiose/vite,
lumiose check and the language server read the route modes the same way (export const mode,
the nearest layout's, then lumiose({ mode })).
// src/routes/books/[slug]/page.stsx (mode `static` or `server`): no warning
export default function Book(props: { params: { slug: string } }) {
const title = props.params.slug.replaceAll("-", " ");
return <h1>{title}</h1>;
}#Example
export function Counter() {
const count = signal(1);
const doubled = count() * 2;
return <button onClick={() => count.update((n) => n + 1)}>{doubled}</button>;
}#How to fix it
Derive with a function or computed, and call it where you need the value:
const doubled = () => count() * 2; // cheap derivations
const total = computed(() => expensive(count())); // cached
return <button>{doubled()}</button>;If you really want the initial value only, read it with count.peek() or untrack(count) to make
that explicit.