Skip to content

Allow $derived and $derived.by in $bindable props declarations #16593

@kran6a

Description

@kran6a

Describe the problem

I stumbled upon this problem on a table component that receives an array of items and an array of available filters as props.

The component manages its own state, meaning actived filters and the list of filtered items were internal component variables.

Now I need to check if all the filtered items meet some criteria from the parent element. To do this, I would make the filtered_items variable bindable. Currently, the filtered_items variable was declaras like this:

<script lang="ts">
    const {items = $bindable(), filters = []} = $props();
    let applied_filters = $state({});//Logic to manage this variable has been omitted
    const filter = ()=>{
        const active_filters = Object.values(applied_filters).filter(Boolean);
        const filtered = items.filter(x => active_filters.every((clause) => clause.evaluate(x)));
        return filtered;
    }
    const filtered_items =  $bindable($derived.by(filter));
</script>

If I wanted to make filtered_items bindable to be able to know which items are currently displayed on the table from the parent component, I would need to use an $effect, rather than a $derived:

<script lang="ts">
    const {items = $bindable(), filters = [], filtered_items = $bindable()} = $props();
    let applied_filters = $state({});//Logic to manage this variable has been omitted
    $effect(()=>{
        const active_filters = Object.values(applied_filters).filter(Boolean);
        const filtered = items.filter(x => active_filters.every((clause) => clause.evaluate(x)));
        filtered_items = filtered;
    });
</script>

This is philosophically wrong. filtered_items is still a $derived but we are using an $effect to do that just because the compiler will complain about something like this:

<script lang="ts">
    const {items = $bindable(), filters = [], filtered_items = $bindable($derived.by(filter))} = $props();
    let applied_filters = $state({});//Logic to manage this variable has been omitted
    const filter = ()=>{
        const active_filters = Object.values(applied_filters).filter(Boolean);
        const filtered = items.filter(x => active_filters.every((clause) => clause.evaluate(x)));
        return filtered;
    }
</script>

Describe the proposed solution

I would like to be able to use $derived and $derived.by in props destructuring. Now that deriveds can be writable I don't see why this shouldn't be allowed.

Having to change from a $derived to an $effect just because you want the variable to be readable from outside seems wrong.

We may want to differentiate between writable deriveds and readonly ones. For this we can use $bindable($derived()) (writable) VS just $derived() (readonly, can still be bound, any initial value provided by the parent component will be ignored).

Importance

nice to have

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions