What is the best way to define a type for a variable within a function, depending on the type of an argument passed to that function in Typescript?

As I delve into developing a custom useFetch composable for a Vue application, the focus seems to shift towards TypeScript. Essentially, my query revolves around conditionally asserting a type to a variable within a function, contingent on the type of an argument supplied to said function.

My understanding of both TypeScript and Vue is still in its nascent stage, leaving me unsure of the feasibility of my request. Should it prove to be unattainable, I am open to any guidance on restructuring the approach.

The crux of the matter lies in having fetchedData assigned as either Ref<T> when a defaultValue (of type T) is provided, or Ref<T | undefined> when no defaultValue is specified. Below is the provided code snippet:

Corresponding snippet of the useFetch composable:

import type { Ref } from "vue";
import { ref } from "vue";

export default function useFetch<T>({
    defaultValue,
}: {
    defaultValue?: T;
} = {}) {
    // The use of 'as Ref<something>' type assertions serves to prevent fetchedData from becoming Ref<UnwrapRef<something>>.
    // This is a Vue-specific behavior. Although, I could opt for shallowRef, I prefer exhausting other possibilities first.
    let temp;
    if (defaultValue !== undefined) {
        temp = ref(defaultValue) as Ref<T>;
    } else {
        temp = ref() as Ref<T | undefined>;
    }

    const fetchedData = temp; // Ref<T | undefined>

    // Code utilizing this reference

    return {
        fetchedData,
    };
}

Usage within a .vue component:

import useFetch from "@composables/useFetch";
import type { ICustomInterface } from "@types";
const { fetchedData } = useFetch<ICustomInterface[]>({
    defaultValue: [],
});
fetchedData.value[0]; // ts18048: fetchedData.value is possibly undefined despite passing default value

Answer №1

When passing argument A to foo(), I need it to return X and when passing argument B, I need it to return Y

For this scenario, utilizing function overloads is usually the way to go.

// Example implementation
function foo(arg: A): X
function foo(arg: B): Y

function foo(arg: A | B): X | Y {
  if (arg.type === 'a') return { x: true }
  return { y: true }
}

If your situation is slightly more complex, consider the following:

export default function useFetch<T>(
  options?: { defaultValue?: undefined }
): { fetchedData: Ref<T | undefined> }

export default function useFetch<T>(
  options: { defaultValue: T }
): { fetchedData: Ref<T> }

export default function useFetch<T>({
    defaultValue,
}: {
    defaultValue?: T;
} = {}) {
    return { fetchedData: ref(defaultValue )}
}

The first overload:

export default function useFetch<T>(
  options?: { defaultValue?: undefined }
): { fetchedData: Ref<T | undefined> }

accepts:

useFetch<MyType>()
useFetch<MyType>({})
useFetch<MyType>({ defaultValue: undefined })

And returns Ref<T | undefined>


The second overload:

export default function useFetch<T>(
  options: { defaultValue: T }
): { fetchedData: Ref<T> }

accepts:

useFetch<MyType>({ defaultValue: somethingHere })

and returns Ref<T>.


Here is a simplified implementation:

export default function useFetch<T>({ defaultValue }: { defaultValue?: T } = {}) {
    return { fetchedData: ref(defaultValue )}
}

With a default value:

const { fetchedData } = useFetch<{ foo: string }[]>({ defaultValue: [] });
fetchedData.value[0]; // valid

Without a default value:

const { fetchedData } = useFetch<{ foo: string }[]>();
fetchedData.value[0]; // expected error

Check playground

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

Modifying the value of an object key with Javascript

The information I am working with is structured as follows: obj = { pref: { language: 'English', } }; My goal is to update the language field to 'Spanish'. ...

There are no headers present in the response from apollo-client

I am currently utilizing a graphql api along with a vue.js frontend that incorporates the apollo client for fetching data from the backend. This setup has been operating smoothly thus far. In each response header, the server sends back a new JWT-Token whi ...

Simulate internationalization for vue using jest

Currently, I am working on setting up jest unit tests for a Vue project within a complex custom monorepo. I am facing an issue with i18n, which I use for translation management in my application. The problem arises with the following code snippet for init ...

Ionic: Fixed button located at the bottom of a specific ion-slide

I've been creating a series of slides with questions, and the final slide serves as a summary of the previously answered questions. I want to ensure that the submit button is always visible at the bottom of this last slide. However, I've encounte ...

Challenges arising from the intersection of Vue's scoped styles and Bootstrap

Currently, I am working on developing an embedded plugin using Vue.js. During the development phase, this plugin is integrated into a html page filled with placeholder text that loads Bootstrap. I recently discovered that one of the elements within my plu ...

Angular does not propagate validation to custom form control ng-select

In my Angular 9 application, I am utilizing Reactive Forms with a Custom Form Control. I have enclosed my ng-select control within the Custom Form Control. However, I am facing an issue with validation. Even though I have set the formControl to be requir ...

Error message "Vue CLI project setup encountering ENOENT error: file or directory does not exist"

I'm currently in the process of creating a new Vue.js app, and I've executed the following command: vue create client After that, I opted for the default template: default (babel, eslint) However, during the setup process, it abruptly stops w ...

Utilize NgRx's dispatch method to pass a payload and update the store

Exploring the world of ngRx is a new journey for me. I am currently in the process of establishing a store that will receive updates triggered by actions from components. NgRx create methods are being utilized to craft actions and reducers for this purpose ...

Vue 3 allows for creating multiple cards with unique contents

I received this outcome: Content duplicated but not cloned as a card element Below is the code snippet <script setup> import { ref } from 'vue'; defineProps({ project: String, }); const projectList = ref([ { img: './src/asse ...

Achieving a scrolling body with a fixed header in a Vue b-table

Currently, I have implemented a b-table element on a webpage to display data from a database. The table is paginated, but feedback suggests that users prefer all information displayed in a single scrolling view. However, the issue arises when I attempt to ...

Guide to connecting a value within a component to an element in App.vue

I'm looking to dynamically assign a class to an element in App.vue based on a property or value from each page component. It's kind of like the opposite of passing a prop, not quite child -> parent or parent -> child. I've tried using ...

Vue - the data within the instance is not defined

One challenge I'm facing is trying to determine the active topic from a menu of topics (such as "About us", "Support"). The goal is to indicate which topic is currently active when it is clicked. Due to limitations in SharePoint 2010 and working withi ...

Generating step definitions files automatically in cucumber javascript - How is it done?

Is there a way to automatically create step definition files from feature files? I came across a solution for .Net - the plugin called specflow for Visual Studio (check out the "Generating Step Definitions" section here). Is there something similar avail ...

Encountering deployment problems with React and TypeScript involving router on Github Pages

After successfully running locally, I encountered a 404 error when deploying the website using "npm run deploy." My application is built with React and TypeScript, utilizing react-router-dom BrowserRouter for navigation between pages. I've spent 7 h ...

Exploring the creation of an Angular service that utilizes HttpClient for making GET requests, with a focus on the different

I've been diving into Angular lately and I'm facing some challenges with handling get requests. If you're interested, you can check out my code on Angular Stackblitz import { HttpClient} from '@angular/common/http'; import { Inject ...

Tracking changes in a textarea with Vuejs

Is there a way to display the Save button when the value in a textarea changes? <template lang="pug"> .modal-task(:style="{display: showDetailsModal}") .modal-task-details .task() .description ...

Issue TS2315: Type 'ElementRef' does not support generics

While attempting to integrate @angular/materials into my application, I encountered a successful compilation with the following error messages: webpack: Compiled successfully. ERROR in node_modules/@angular/material/button-toggle/typings/button-toggle.d.t ...

What is the best way to stop webpack from generating typescript errors for modules that are not being used?

The directory structure is set up as follows: └── src ├── tsconfig.json ├── core │ ├── [...].ts └── ui ├── [...].tsx └── tsconfig.json Within the frontend, I am importing a limi ...

Pinia throws a useStore is not callable error

I have been trying to resolve the issue with (0 , pinia__WEBPACK_IMPORTED_MODULE_1__.useStore) is not a function but unfortunately, I haven't been able to find a solution. Can anyone point out what mistake I am making? Here is my store.js code: im ...

Exploring the process of iterating through arrays within an object in vue.js using the v-for directive

Is there a way to iterate through an output object using the v-for template in Vue.js? new Vue({ el: app, data: { output: { player: [1, 5, 61, 98, 15, 315, 154, 65], monster: [14, 165, 113, 19, 22], }, }, }); <script src= ...