Laaouatni Anas
Laaouatni Anas

Reputation: 3855

in a Svelte Writable store, how can I set a different Type for getter/setter in Typescript/Svelte?

In one of my projects, I am using SvelteKit, Tailwind CSS, and especially TypeScript.

During the development, I understood that I must use a writable store, instead of passing the props.

Everything works fine, but the typing system is not as dynamic as it should be.


Let me explain,
To make you understand, where I want to go,
By oversimplifying my actual scenario:

I have an array with child objects with the same shape, like this:

[
  {
    id: "myUniqueId",
    type: "MyType",
    properties: {
      foo: "bar",
    },
  },
  {
    ...
  },
  {
    ...
  },
];

this array is passed inside the writable store like this:

import { writable } from "svelte/store";
import { exampleArray } from "$lib/data/exampleData";
export const myAppStore = writable<TypeMyAppStore>([]);

myAppStore.set(exampleArray);

with this code, everything is right, even in typescript

but, now I want to make the user to explicitly,
to don't add the id, so the object becomes something like this:

{
  type: "MyType",
  properties: {
    foo: "bar",
  },
},

the id will be added dynamically using .set() and .update()

appStore.subscribe((value) => { 
  value.forEach((thisObj) => {
    thisObj.id = generateUniqueId();
  });
  appStore.set(value);
});

with this approach, I can basically add the IDs dynamically when the user pushes a new object inside my store.

the problem is in the IntelliSense autocompletion and typescript, because:

the code and app is running fine,
but I need a way to make the writable store, accept only type without id, but in intellisense suggest the id.

if you think about it, we can say that:

if we want to talk about the same situation but in another context, is like this:

function foo(input: TypeMyInput): TypeMyOuput {}
//This is literally what I want to implement in my svelte store,
//Accept one input with a certain type
// change that array using `.subscribe()`/`.set()`
// then make by svelte store, to think about another type (and not give errors anymore only if the type is not respected, and also give me autocompletion and intellisense)

//For now I solved only the input, but the output not.
// writable<TypeMyInput>([])

How we can do both (I mean without union type) in a way that can accept only one type, but return another type?


all the code above is not nearly my real-world code, is an oversimplified example for you.

Remember

I hope you understand my situation.

if you still don't understand what I want, I can give you the link to my GitHub repo https://github.com/Laaouatni/laaCAD/blob/development/app/src/lib/Data/appStore/appStore.ts

Upvotes: 0

Views: 1268

Answers (2)

Stephane Vanraes
Stephane Vanraes

Reputation: 16451

You might want to look at custom stores these allow you to create your own stores (that you can subscribe to with $) while adding some extra functionality.

For example this code

import { writable } from 'svelte/store';

const createStore = <T extends { id: string }>() => {
  const store = writable<T[]>([]);

  return {
    subscribe: store.subscribe,
    add: (value: Exclude<T, 'id'>) => {
      const value_with_id = {
        ...value,
        id: crypto.randomUUID() as string
      };
      store.update(($store) => {
        $store.push(value_with_id);
        return $store;
      });
    }
  };
};

export default createStore;

does almost what you want:

  • define a store of items (with id) for which the subscribed value included the id
  • define an "add" method that forces you to only add items without id

I made it with an add function just because it feels weird that everytime you change the store you would change all ids ? (I wonder if this wouldn't also cause an infinite loop for that matter because you are changing all items everything an item changes?) You also ask for a setter without id, but then use .set with values that have an id, which seems a bit strange :)

Upvotes: 1

brunnerh
brunnerh

Reputation: 185117

Stores, when used with $-syntax, behave like regular variables, variables can only have one type.
So I don't think you get around wrapping the value in an object.

Example:

function dualTypedWritable<TGet, TSet>(
    initialValue: TSet,
    onSet: (newValue: TSet) => TGet,
) {
    const store = writable<TGet>(onSet(initialValue));
    return derived(store, storeValue => ({
        get value(): TGet { return storeValue; },
        set value(newValue: TSet) { store.set(onSet(newValue)); }
    }));
}
    
const items = dualTypedWritable(
    [{
        type: "MyType",
        properties: { foo: "bar" },
    }],
    newItems => newItems.map(item => ({
        id: Math.random().toString().slice(2),
        ...item,
    })),
);

Every access is then via $items.value.

Alternatively, you could use functions rather than a property with getter and setter.

Upvotes: 1

Related Questions