Reputation: 3855
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:
id
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:
id
,id
.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
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:
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
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