How can I utilize Pinia and TypeScript to set the State using an Action?

I have a Pinia + TypeScript store named user.ts with the following structure:

import { User } from 'firebase/auth';
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
  state: () =>
    ({
      displayName: null,
      email: null,
      emailVerified: null,
      isAnonymous: null,
      metadata: null,
      multiFactor: null,
      phoneNumber: null,
      photoURL: null,
      providerData: null,
      providerId: null,
      refreshToken: null,
      tenantId: null,
      uid: null,
    } as unknown as User),
  actions: {
    setPhotoURL(photoURLData: string | null) {
      this.photoURL = photoURLData; //<-- ERROR HERE
    },
  },
});

The state object represents a FireBase User.

I'm trying to update the photoURL value using the setPhotoURL() action.

However, I encountered a TypeScript error related to the readonly property of photoURL:

Cannot assign to 'photoURL' because it is a read-only property. ts(2540)

Can anyone help me understand what I'm doing wrong?

Is this the correct approach for updating state in this setup?

Answer №1

To eliminate the readonly modifier, you can achieve this by mapping the type :

Mapping Modifiers

When mapping types, there are two extra modifiers that come into play: readonly and ?, influencing mutability and optionality.

You have the ability to add or remove these modifiers using a prefix of either - or +. If no prefix is added, then + is assumed.

// Eliminates 'readonly' attributes from a type's properties
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

If your type features nested properties that need to be made writable, recursive mapping is required:

type CreateMutable<T> = { -readonly [P in keyof T]: CreateMutable<T[P]> }

Subsequently, apply it when typing the store's user property:

import type { User } from 'firebase/auth'
import { defineStore } from 'pinia'

type CreateMutable<T> = { -readonly [P in keyof T]: CreateMutable<T[P]> }

export const useUserStore = defineStore('user', {
  state: () => ({ user: {} as CreateMutable<User> }),

  actions: {
    setPhotoURL(photoURLData: string | null) {
      this.user.photoURL = photoURLData
    },
  },
})

Answer №2

'firebase/auth' defines the User interface with readonly properties. When you forcefully assign this interface to your state object, TypeScript believes you.

By spreading it, you can remove the readonly from its properties while still maintaining type inference:

export const useUserStore = defineStore('user', {
  state: () => ({ ...({
    displayName: null,
    email: null,
    emailVerified: null,
    isAnonymous: null,
    metadata: null,
    multiFactor: null,
    phoneNumber: null,
    photoURL: null,
    providerData: null,
    providerId: null,
    refreshToken: null,
    tenantId: null,
    uid: null,
  } as unknown as User) }),
  actions: {
    //...
  }
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What methods can I use to combine existing types and create a brand new one?

Is there a way to combine existing types to create a new type in TypeScript? `export type Align = 'center' | 'left' | 'right' export type Breakpoints = ‘sm’ | ‘md’` I want to merge the Align and Breakpoints types to ...

Enhancing Readability of Public Static Member Variables in Typescript

In my node application, I am utilizing typescript and winston for logging purposes. One key element of my setup is the "Logger" class which consists of a "logger" member and an "init()" function. By exporting this class, I understand that the "logger" memb ...

Troubleshooting: Vue.js component events not being detected in TypeScript

I'm encountering an issue with receiving events from a barcode reader, which I heavily referenced from a GitHub repository. The problem lies in the fact that the events emitted by the BarcodeScanner component are not being captured by the enclosing c ...

The most effective method for transferring a JavaScript object between a TypeScript frontend and a Node.js backend

I need some advice on how to effectively share a JavaScript object between my Angular2 with Typescript frontend and NodeJS backend in an application I'm working on. Currently, I am using a .d.ts file for the frontend and adding a module.exports in the ...

Creating a loading screen in Angular 4: Insert an item into the HTML and then set it to disappear automatically after

I'm dealing with a loading screen that typically takes between 15-30 seconds to load about 50 items onto the page. The loading process displays each item on the page using the message: Loading item x For each data call made to the database, an obser ...

An error occurred while trying to add a property to an array because the object is not extensible: TypeError -

In my code, there is an object named curNode with the following structure: { "name": "CAMPAIGN", "attributes": {}, "children": [] } I am attempting to add a new node to the object like this: curNode!.children!.push({ name: newNodeName, ...

Tips for dynamically adding events to a tag within a custom grid component

Presenting a query: `<tr @click="rowevent != null?rowevent(row,this.$el):''" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">` I am wondering how I can dynamically add events based on data (props) without explicitly writing each ev ...

Tips for showcasing saved images in Spring Boot with Angular 4

I am currently utilizing Spring Boot in combination with Angular 4. The issue I am facing involves uploading an image to the project location. However, upon attempting to view the uploaded image, it does not display correctly and instead throws an error. H ...

Transforming API Response into a structured format to showcase a well-organized list

When I make an API call for a list of properties, the data comes back unorganized. Here is how the data from the API looks when stored in vuex: posts:[ { id: 1; title: "Place", acf: { address: { state: "Arkansas", ...

Angular 4/5 | Custom Dropdown Component

I have been working on a custom dropdown directive in Angular that I can attach to any DOM element. Below is the code for my directive: import { Directive, HostListener } from '@angular/core'; @Directive({ selector: '[appDropdown]' ...

Angular loop using an HTTP GET request is producing garbled information

Currently, I have a loop that includes an http GET request. This is the sample loop code: for (let index = 0; index < datas.length; index++) { let car = datas[index].smiles; console.log('or--> ' + car); this.subscr = this.CarServ ...

Creating a web application using Aframe and NextJs with typescript without the use of tags

I'm still trying to wrap my head around Aframe. I managed to load it, but I'm having trouble using the tags I want, such as and I can't figure out how to load a model with an Entity or make it animate. Something must be off in my approach. ...

Coverage of code in Angular2 using Qunit

Is there a reliable code coverage measurement tool or framework that can easily be integrated to assess the code coverage of Angular2-TypeScript code with QUnit tests? I have come across some frameworks like remap-istanbul, blanket.js etc., but these fram ...

Obtaining a value from an input field in Vue.js

Just starting out with Vue and could use a hand extracting a value from an input field: Here's what I have in my form: <input type="hidden" id="groupId" value="1"> If I were using jQuery, I would do the following: ...

Rendering content on the server side and creating a cached version of the index.html file using Vuejs and Nodejs

Having multiple websites (site1.com, site2.com) connected to a single server poses an interesting challenge. I am able to capture the domain name when a user enters a site, and based on that domain name, I fetch appropriate JSON data from an API to display ...

Resolving TypeScript errors when using the Mongoose $push method

It appears that a recent package upgrade involving mongoose or @types/mongoose is now triggering new typescript errors related to mongoose $push, $pull, $addToSet, and $each operators. For instance: await User.findByIdAndUpdate(request.user._id, { $ ...

What is the best way to delete markers from a leaflet map?

I need to remove markers from my map. I am looking to create a function that will specifically clear a marker based on its ID. I am utilizing Leaflet for the map implementation. Here is my function: public clearMarkers(): void { for (var id in this. ...

The specified 'detail' property cannot be found on the given type '{}'. Error code: 2339

I encountered the error mentioned in the title while working on the code below. Any suggestions on how to resolve this issue? Any assistance would be greatly appreciated! import { useHistory } from "react-router-dom"; let h ...

Tips for using $apply and $digest in Angular 4 and IONIC 3?

I am currently working on developing an application using IONIC 3, but facing challenges with the view not refreshing properly. During my experience with Angular 1X, I used to use $apply and $digest to resolve similar issues. How can I achieve the same in ...

Identifying the [__ob__: Observer] in Vue: A Beginner's Guide

When I work with my component, I iterate over a data property that holds an object to verify if any values have been set. One of the values within this object is an array. However, when the array is empty and I attempt to log its value, the console shows: ...