I have a filter variable of type "all" | "active" | "completed"
, which I initialize to 'all'
(in fact it's reactive, declared with $state('all')
but I can reproduce the issue with a plain variable
so this works OK:
let filter: "all" | "active" | "completed" = "all"; // same thing happens if I use $state("all")
function filteredFn() {
console.log({ filter }); // => (property) filter: "all" | "active" | "completed"
if (filter === "active") return todos.filter((t) => !t.completed); // OK!
if (filter === "completed") return todos.filter((t) => t.completed); //OK!
return todos;
}
but if I try to do the same in side a $derived.by()
rune, filter is assumed to be of type 'all' as if it were declared with as const
, like this:
let filter: "all" | "active" | "completed" = "all";
let filtered = $derived.by(() => {
console.log({ filter }); // => (property) filter: "all" !!!! WRONG!!!
if (filter === "active") return todos.filter((t) => !t.completed);
// This comparison appears to be unintentional because the types '"all"' and '"active"' have no overlap.ts(2367)
if (filter === "completed") return todos.filter((t) => t.completed);
// This comparison appears to be unintentional because the types '"all"' and '"completed"' have no overlap.ts(2367)
return todos;
});
I can fix it with:
let filter = "all" as "all" | "active" | "completed";
or with the $state rune:
let filter = $state<"all" | "active" | "completed">("all");
but I don't know why it doesn't work the same way it works with regular functions
It this a bug or am I missing something???
The problem should be control flow analysis.
If the variable is not changed anywhere, its type is narrowed to just that single value. This happens more often with a regular $derived
since that does not even have a function scope, so more aggressive analysis is possible.
As soon as you e.g. have a binding to filter
or set it somewhere in the <script>
the issue should disappear.