Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Access svelte writable located in object

In shadcn ui port for svelte 5Select component (using bitsui select) bind:value asks for state(string), but Writable<string> works too as well, so bind:value={$theme or $language} is properly getting and setting value. But my settings component works with additional setting passed to it as props with store as one of elements of object and i have trouble getting writable properly work from object.

<script lang="ts" module>
    interface SelectItem {
        label: string;
        labelRu: string;
        value: string;
    }

    export interface Setting {
        store: Writable<string>;
        name: string;
        nameRu: string;
        values: SelectItem[];
    }
</script>

<script lang="ts">
    import * as Select from '$shared/components/ui/select/index.js';

    import { theme } from '$shared/stores/theme';
    import { language } from '$shared/stores/language';
    import { derived, type Writable } from 'svelte/store';

    let { additionalSettings = null }: { additionalSettings?: { [key: string]: Setting } | null } =
        $props();
    let clazz = $state('');
    export { clazz as class };

    let settings: { [key: string]: Setting } = {
        theme: {
            store: theme,
            name: 'Theme',
            nameRu: 'Тема',
            values: [
                { label: 'Light', labelRu: 'Светлая', value: 'light' },
                { label: 'Dark', labelRu: 'Темная', value: 'dark' }
            ]
        },
        language: {
            store: language,
            name: 'Lingo',
            nameRu: 'Язык',
            values: [
                { label: 'English', labelRu: 'Английский', value: 'en' },
                { label: 'Russian', labelRu: 'Русский', value: 'ru' }
            ]
        }
    };

    settings = { ...settings, ...(additionalSettings ?? {}) };

    const settingLabel = Object.fromEntries(
        Object.entries(settings).map(([key, setting]) => [
            key,
            derived([setting.store, language], ([$store, $language]) => {
                const selectedValue = setting.values.find((value) => value.value === $store);
                return $language === 'ru' ? selectedValue?.labelRu : selectedValue?.label;
            })
        ])
    );
</script>

<div
    class="{clazz !== '' ? clazz + ' ' : ''}flex min-w-[240px] flex-col space-y-4 px-2 py-4 text-sm"
>
    {#each Object.entries(settings) as [key, setting]}
        <div class="flex items-center space-x-2">
            <p class="min-w-[48px]">{$language === 'ru' ? setting.nameRu : setting.name}:</p>
            <Select.Root type="single" name={key} bind:value={$setting.store}>
                <Select.Trigger>
                    {#await settingLabel[key] then label}
                        {(label ?? $language === 'ru')
                            ? 'Выберите ' + setting.nameRu
                            : 'Select ' + setting.name}
                    {/await}
                </Select.Trigger>
                <Select.Content>
                    {#each setting.values as value}
                        <Select.Item value={value.value}>
                            {$language === 'ru' ? value.labelRu : value.label}
                        </Select.Item>
                    {/each}
                </Select.Content>
            </Select.Root>
        </div>
    {/each}
</div>

I’m newby to Svelte 5 reactive functionality, so maybe i’m asking something easy fixing. Sorry for this(

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

Stores can only be used with $-syntax if they are declared at the root of the component which is not the case here since the store variable comes from an #each block.

You can extract the contents of the #each to a new component, pass the store to said component then it will satisfy the necessary criteria there.

But generally I would not recommend using stores at all in Svelte 5 unless it’s a third party dependency, though even then you can use the fromStore helper function to turn the store into a state object. This should be possible here as well.

A simplified example:

<script>
    import { writable, derived, fromStore } from 'svelte/store';

    const value = writable(2);
    const double = derived(value, v => v * 2);

    const settings = [
        { name: 'Original', store: value },
        { name: 'Doubled', store: double },
    ];
</script>

{#each settings as { name, store }}
    {@const state = fromStore(store)}
    <label>
        {name}
        {#if 'set' in store}
            <input type=number bind:value={state.current} />
        {:else}
            {state.current}
        {/if}
    </label> <br>
{/each}

Stores: {$value} * 2 = {$double}

Playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading