#How do I make my array of Objects conform to my Type?

55 messages · Page 1 of 1 (latest)

potent merlin
#

I have a Type that occurs throughout most of my app, especially in my Pinia store. The Type is defined as follows:

#

export type Transaction = {
id: number;
description: string;
transactionType: string;
amount: number;
}

#

Now, I have the following in my Pinia store:

#

export const useTrackerStore = defineStore('storeTracker', {
state: () => ({
transactions: [
{ "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
{ "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
{ "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
]
}),

#

I want to make sure that the transactions array conforms to my Type so that there is no chance that any of the amounts are strings or and of the strings numbers. But I can't figure out the syntax to make each of the objects in the array be of type Transaction. (I have successfully imported Transaction into the Pinia store.)

#

Never mind: I stumbled on it in asking the question! Just append as Transaction[] after the last square bracket!!

ivory thorn
#

You should avoid using as, this tells TS to treat that value as that type even if it is not

#

You can probably use satisfies Transaction[] for the same effect, safely

potent merlin
#

Yeah, that only made the squiggles go away but did NOT force me t o put strings in the string fields or numbers in the number fields.

ivory thorn
#

I suspect that defineStore will take a type argument letting you set the type there instead. Something like defineStore<{ transactions: Transaction[] }>('storeTracker'... But that's a loose guess

potent merlin
#

Do I put the satisfies Transaction[] in the same place as I put the as Transaction[]? (How are you getting the different font when you indicate code?)

ivory thorn
#

If you hover it on control click it should get more info including whether it has type arguments, but it could be complicated

#

Look for <...> after the function name

#

When hovering it will show any type arguments that were automatically inferred from the values you provided

ivory thorn
#

I'm using backticks

#

`

#

```ts
...
```
Is useful too

potent merlin
#

Okay, I'll try that first.

ivory thorn
#
export const useTrackerStore = defineStore('storeTracker', {
    state: () => ({ 
        transactions:  [
             { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
             { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
             { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
        ] 
    }),
})
#

I don't know if you're getting code highlighting there. Should be but not showing up here

#
const a = 1
potent merlin
#

You're right: it is kinda complicated - and maybe unnecesssary. It gives the impression that it already understands what types the individual fields should be:

warm galeBOT
#
const a = 1
//    ^? - const a: 1
ivory thorn
#

```ts twoslash

potent merlin
#

(alias) defineStore<"storeTracker", {
transactions: {
id: number;
description: string;
transactionType: string;
amount: number;
}[];
}, {
getNewId(): number;
getBalance(): number;
getIncome(): number;
getExpense(): number;
}, {
addTransaction(newTransaction: Transaction): void;
deleteTransaction(idOfTransactionToBeDeleted: number): void;
}>(id: "storeTracker", options: Omit<...>): StoreDefinition<...> (+2 overloads)
import defineStore
Creates a useStore function that retrieves the store instance

@param id — id of the store (must be unique)

@param options — options to define the store

Yet there's nothing preventing me from writing a salary of "dog" in the salary field.

ivory thorn
#

Ah

potent merlin
#

Sorry, I meant to add the backtics where needed.

ivory thorn
#

So it has 4 type args

ivory thorn
#

However if you specify any type arguments manually, then you have to provide them all, unless they have default values

#

Satisfies seems to be working well, so maybe* leave it at that for now

potent merlin
#

Yeah, I keep being tempted to force fields to be a specific type when the inference will do the job "automagically". I've got to break myself of that tendency and leave well enough alone. This is just a prototype anyway so no one else is going to be coding in it. There's no chance someone is going to come along and code "dog" for the amount in transaction 9. Maybe in a real project with multiple coders, some of whom are brand new to coding, it might be more reasonable to force types on the fields but not in my current situation.

#

Thanks for the sanity check Bert!

ivory thorn
#

I like to provide explicit types to make sure everything is correct. Either const x: Transaction = { ... } or const y = { ... } satisfies Transaction. Also annotating function return types explicitly. Preferences vary.

potent merlin
#

I've only just seen your final remark and tried to incorporate it in my code, with mixed results. Maybe you can help me understand why? Starting with:

#
    state: () => ({ 
        transactions: [
             { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
             { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
             { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
        ] 
    }),```
#

I changed it to:

#
    state: () => ({ 
        transactions: Transaction = [
             { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
             { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
             { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
        ] 
    }),```
#

and got: 'Transaction' only refers to a type, but is being used as a value here.ts(2693) any.

#

Same message when I added [] immediately after Transaction

#

However, when I did this:

#
    state: () => ({ 
        transactions: [
             { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
             { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
             { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
        ] satisfies Transaction[]
    }),```
#

there were no squiggles until I changed the amount value to dog, which was exactly what I wanted. Why did the second approach work but the first approach fail?

ivory stirrup
#

name: Type = value is for assignment

ivory thorn
#

use ```ts instead of ``` please

ivory thorn
# potent merlin ```export const useTrackerStore = defineStore('storeTracker', { state: () =>...

The problem here is that you tried to put a type annotation into the middle of a value definition.

The value here is

{
    transactions: [
         { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
         { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
         { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
    ]
}

You can't put a type annotation with : T into the middle of that, : is already used to specify the value for the transactions property.

defineStore is a generic function, meaning that either types can be specified like defineStore<A, B>(...) or it will infer types from the arguments.

Three options here.
1 You can create your object separately.

const transctions: Transaction[] = [
     { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
     { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
     { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
]
export const useTrackerStore = defineStore('storeTracker', {
    state: () => ({
        transactions: transactions
    })
})

2 Use satisfies like you did.

3 My preferred option if supported, specify the type as a type argument to the function

export const useTrackerStore = defineStore<'storeTracker', Transaction[]>('storeTracker', {
    state: () => ({
        transactions: [
             { "id": 9, "description": "Salary", "transactionType": "Income", "amount": 3000.00 },
             { "id": 27, "description": "Air Fare", "transactionType": "Expense", "amount": 700.00 },
             { "id": 6, "description": "Taxi", "transactionType": "Expense", "amount": 30.00 }
        ]
    }),
})

But whether you can do 3 depends on the implementation of defineStore. Define store actually has 4 generic type arguments, you need to define the 1st to get to the 2nd.

#

And if the 3rd and 4th don't have defaults, you'll have to specify them too. So if they don't have defaults this option probably becomes too cumbersome here. If they do then it's OK.

#

A practical example of using generic arguments: creating an empty array of type number[].
There are all the same:

const arr1: number[] = []
const arr2: number[] = new Array()
const arr3 = new Array<number>()
#

highlighting is weird, but don't worry

#

So with generic type arguments, you generally let the compiler infer them (work them out from the values you pass) automatically. You don't need to do double work. But in a situation like yours where there are no checks to make sure that your transactions match Transaction[], you can use it there to set an explicit constraint and cause a check. But all of those options shows are pretty much the same. Preferences might depends on what else you're doing with the objects and types in the app.

#

!hb generics

warm galeBOT
ivory thorn
#

You probably don't want to be writing generics yet. But it might help to understand the basic idea and the syntax, as you'll use them often in libraries.