Enhance Your Vue3 Experience with Type-Safe Axios Requests

I have been exploring the concepts of "type safety" in my project using Vue3, TypeScript, and Axios.

Although it seems straightforward, I can't shake the feeling that I am overlooking something obvious!

To start off, I defined an interface called Book:

Book.ts

interface Book {
  id: string;
  userId: string;
  title: string;
  content: string;
}

export default Book;

In addition, I set up a basic service to fetch JSON data like this:

 {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },

(Please note: The JSON uses body instead of content as specified in the interface)

DataService.ts

import axios, { AxiosResponse } from "axios";
import Book from "@/interfaces/Book";

class DataService {
  async FetchBooks(): Promise<Book[]> {
    let response: Book[] = [
      { id: "1", userId: "someone", title: "A title", content: "Some content" }
    ];
    try {
      const val = await axios.get<Book[]>(
        `https://jsonplaceholder.typicode.com/posts`
      );

      if (val.data && val.status === 200) {
        response = val.data;
      }
    } catch (error) {
      console.log(error);
    }

    return response;
  }
}

export default new DataService();

My primary concern is why the response still includes "body" even though it's not part of the Book interface. I assumed it would be omitted since I specified the type as Book in the axios get request.

Another question arises regarding why I can use {{ book.body }} in the template below, considering that I'm treating the response as a Book object without defining "body" in the interface.

Book.vue

<template>
  <div v-for="book in books" :key="book.id">
    <div class="mb-2">
      {{ book.body }}
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import DataService from "@/services/DataService";

// eslint-disable-next-line no-unused-vars
import Book from "@/interfaces/Book";

export default defineComponent({
  async setup() {
    let books: Book[] = [];

    await DataService.FetchBooks().then(data => (books = data));

    return { books };
  }
});
</script>

Answer №1

Why does the response value from the service still contain "body" even though it does not exist on the Book interface?

In TypeScript, interfaces serve as contracts for entities.

An object can have additional properties beyond what's defined in the contract, but the compiler only requires the presence of the essential ones and matching types. There are exceptions where TypeScript is less forgiving. Learn more.

I had hoped that "body" would be dropped when passing the type Book to the Axios get request.

It will persist unless you explicitly redefine the response items with "body" omitted or renamed to "content"—only then will the contract operate optimally. For example:

const val = await axios
  .get<Array<Book & { body: string }>>(
    `https://jsonplaceholder.typicode.com/posts`
  )
  .then(res => res.data.map(
    ({ body, ...props }) => Object.assign({ content: body }, props)
  ));

To streamline type assertion, consider adding a DTO for the original Book object that includes an intersection type for the raw "body" property. For instance:

interface IBook {
  id: string;
  userId: string;
  title: string;
  content: string;
}

interface IBookRaw extends Omit<IBook, 'content'> {
  body: string;
}

Additionally, enhance data mapping by using a class that implements this interface, such as:

class Book implements IBook {
  id: string;
  userId: string;
  title: string;
  content: string;

  constructor({ id, userId, title, body }: IBookRaw) {
    this.id = id;
    this.userId = userId;
    this.title = title;
    this.content = body;
  }
}

const val = await axios
  .get<IBookRaw[]>(
    `https://jsonplaceholder.typicode.com/posts`
  )
  .then(res =>
    res.data.map(item => new Book(item))
  );

How am I able to reference {{ book.body }} below, even though I'm handling the response as a Book and "body" is not in the interface?

Regarding template interpolation, if utilizing Vetur, enable vetur.experimental.templateInterpolationService for linting hints. Without it, there won't be type checking and the result will likely be undefined.

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

Mocking a promise rejection in Jest to ensure that the calling function properly handles rejections

How can I effectively test the get function in Jest, specifically by mocking Promise rejection in localForage.getItem to test the catch block? async get<T>(key: string): Promise<T | null> { if (!key) { return Promise.reject(new Error(&apo ...

Is it possible to utilize splice and push methods on a computed array in VueJS?

Is it possible to programmatically change a computed array? Perhaps by splicing an item from an array and pushing it to another position? list_array = [{list_id: 1, status: 'good', data: {item_id: 1, event: 'jumping'}] The code for sp ...

What is the proper way to implement v-model with Vuex in <select> elements?

I included a <select> element in my design: <select v-model="amount" required> <option value="10">10</option> <option value="20">20</option> <option value="25">25</o ...

Retrieving Vue data from parent components in a nested getter/setter context

<template> <div id="app"> {{ foo.bar }} <button @click="meaning++">click</button> <!--not reactive--> <button @click="foo.bar++">click2</button> </div> </templ ...

Ensure to pass the correct type to the useState function

I have a basic app structured like this import React, { useState } from 'react' import AddToList from './components/AddToList' import List from './components/List' export interface IProps{ name: string age: number url: ...

Ways to detach a Vue.js instance from the Document Object Model

Is it possible to remove a Vue.js instance from the DOM? I need to mount and unmount the Vue instance based on certain conditions. ...

Incorporate input-style labeling into Vuetify's v-card component

https://i.stack.imgur.com/CMs0l.png Is there a way to add a label similar to a v-input's label to a v-card, just like in the example of "select" above? If so, how can this be achieved? I was considering appending a label using code, but I believe t ...

What is the process for deconstructing errors from the RTK Query Mutation Hook?

Currently, I am utilizing redux toolkit query for handling my data fetching API. One issue that I have encountered is with error handling. When an error is returned from the server, it comes in as an object with nested data. However, when trying to access ...

What is the best way to transition from using merge in a pipe in v5 to v6?

Currently, I am following the conversion guide found here and I am attempting to convert the merge function used in a pipe according to this guide. However, after making the changes, it does not work as expected. This is the code snippet I am using to exp ...

Issue encountered when utilizing v-model within Vue version 3

When using v-model in Quasar/Vue 3, ESLint generates the following error message: The component's model can be accessed either by using this property (together with a 'update:modelValue' event listener) or by utilizing the v-model directive ...

Having trouble accessing a local JSON file in electron-vue

I am attempting to retrieve a local JSON file in my project. I have tried the following: import axios from 'axios'; import userDataJson from './../data/userData.json'; export const userDataControllerMixin = { data() { return { ...

In Typescript, ambient warnings require all keys in a type union to be included when defining method parameter types

Check out this StackBlitz Example Issue: How can I have Foo without Bar, or both, but still give an error for anything else? The TypeScript warning is causing confusion... https://i.stack.imgur.com/klMdW.png index.ts https://i.stack.imgur.com/VqpHU.p ...

Angular's counterpart to IWebProxy

When using C#, I am able to: public static IWebProxy GetWebProxy() { var proxyUrl = Environment.GetEnvironmentVariable("HTTPS_PROXY"); if (!string.IsNullOrEmpty(proxyUrl)) { var proxy = new WebProxy { Address = new Ur ...

How about utilizing React's conditional rendering feature?

I'm currently working on a component that displays tournaments and matches, and I'm facing a challenge in implementing a filter option for users to select tournaments by 'league', while still displaying all tournaments if no 'leagu ...

Create a nested array of subcategories within an array object

Currently, I am working on integrating Django Rest and Angular. The JSON array received from the server includes category and subcategory values. My goal is to organize the data such that each category has its related subcategories stored as an array withi ...

Incorporating the "+ " icon in Vuejs Dropzone to indicate the presence of existing images

Looking to enhance my Vue-dropzone file uploader component by adding an icon or button labeled "Add more images" when there are already images present in the dropzone. This will help users understand that they can upload multiple photos. Any suggestions on ...

Subtracting Arrays Containing Duplicates

Imagine having two arrays defined like this: const A = ['Mo', 'Tu', 'We', 'Thu', 'Fr'] const B = ['Mo', 'Mo', 'Mo', 'Tu', 'Thu', 'Fr', 'Sa&ap ...

Having trouble resolving the '@angular/material/typings/' error?

I am currently working on tests for an angular project and encountering errors in these two test files: https://pastebin.com/bttxWtQT https://pastebin.com/7VkirsF3 Whenever I run npm test, I receive the following error message https://pastebin.com/ncTg4 ...

Adjust the selected value in real-time using TypeScript

Hey there, I've got a piece of code that needs some tweaking: <div> <div *ngIf="!showInfo"> <div> <br> <table style="border: 0px; display: table; margin-right: auto; margin-left: auto; width: 155%;"& ...

Ways to obtain a tab and designate it as the default in angular when using angular material Tabs

I am facing an issue with accessing tabs within a nested component. The parent component contains the tab feature and to reach the tabs inside the child component, I am using the following code: document.querySelectorAll('.mat-tab-group'); The a ...