vuejs3piniavue-reactivity

Pinia and Vue 3 using reactive arrays to store fixed amount of strings


Omitted import statements

Pinia store: inputData.ts

interface State {
    userInputValues: [
        string, string, string, string,
        string, string, string, string,
        string, string, string, string,
        string, string, string, string,
    ]
}


export const useInputDataStore = defineStore("inputData", {
    state: (): State => {
        return {
            userInputValues: [
                "", "", "", "",
                "", "", "", "",
                "", "", "", "",
                "", "", "", "",
            ]
        }
    }
})

I know there should be 16 strings in the array, so I defined an interface

I have a component here:

<script setup lang="ts">

const store = useInputDataStore();
const { userInputValues } = storeToRefs(store);


const props = defineProps<{
    inputId: number
}>();

// userInputValues is a Ref<[string, string, string...]>
// But how do I achieve it where I each string is a ref
// or userInputValues as Ref<[Ref<string>, Ref<string>, Ref<string>...]

// So here, inputBoxModel is a string not a Ref<string>
const inputBoxModel = userInputValues.value[inputId]

</script>

<template>
    <input v-model="inputBoxModel" />
</template>

What I'm trying to do

I want to have an array of 16 Ref<string>

But...

When I access the array, whats returned is a string, not a Ref<string>

So how can I make it reactive?


Solution

  • I would define a fixed size array type as:

    type FixedSizeArray<N extends number, T> = {
      length: N
    } & ReadonlyArray<T>
    

    which would make the store look like:

    interface State {
      form: FixedSizeArray<16, string>
    }
    
    export const useFormStore = defineStore('form', {
      state: (): State => ({
        form: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
      })
    })
    

    And then you can import form in any component and use it like you would any other store state property.
    Working example: https://codesandbox.io/p/sandbox/sixteen-form-fnmjwg

    Note: in the example I thought TS was smart enough to figure out that Array.from({ length: 16 }).map(() => '') satisfies FixedSizeArray<16, string> but it gives the following error 1:

    Type 'string[]' is not assignable to type 'FixedSizeArray<16, string>'.
      Type 'string[]' is not assignable to type '{ length: 16; }'.
        Types of property 'length' are incompatible.
          Type 'number' is not assignable to type '16'.ts(2322)
    

    For now, the only options are:
    a) a type assertion

    form: <FixedSizeArray<16, string>>Array.from({ length: 16 }).map(() => '')
    

    b) repetition:

    form: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
    

    c) moving the type assertion into a function

    As for your issues with reactivity, the question isn't clear enough. See App.vue for implementation details in the above example.


    1 - I've asked it as a separate question