Consider the potential for receiving an undefined return when a default value is not specified

I developed a React hook that makes a request to a remote API and returns a value. By default, when the API is fetching, the returned value is set to undefined.

However, I added a new option that allows for setting a default value - so if the API is still in the process of fetching, the default value will be returned instead.

My question is, can TypeScript automatically deduce that when defaultValue is provided, then the return type cannot be undefined?

This is the code I have written so far:


interface UseMyHookOptions<T> {
  defaultValue?: T
}

type UseMyHookReturn<T> = T extends undefined 
  ? [T | undefined, (data: T) => void] 
  : [T, (data: T) => void]

function useMyHook<T>(key: string, options: UseMyHookOptions<T> = {}): UseMyHookReturn<T>{
  const defaultValue = options?.defaultValue

  const remoteValue = useFetch<T>(`/api/${key}`)

  const setValue = (value: T) => { console.log(value) }

  const value = remoteValue ?? defaultValue;
  return [value, setValue] as UseMyHookReturn<T>;
}

Examples:

// Expected type for `foo`: T   (since defaultValue is provided)
//                      actual type: T
const [foo, setFoo] = useMyHook<string>('foo', { defaultValue: 'something' });

// Expected type for `bar`: T | undefined   (since no defaultValue is specified)
//                       actual type: T
const [bar, setBar] = useMyHook<string>('bar');

Playground

Answer №1

If you're looking to handle different scenarios based on the parameters provided, consider implementing function overloading.

Why not try something like this?

interface CustomHookOptions<T> {
  defaultValue?: T
}

type CustomHookResult<T> = [T, (value: T) => void]

function customHook<T>(key: string): CustomHookResult<T | undefined>;
function customHook<T>(key: string, options: CustomHookOptions<T> & { defaultValue: T }): CustomHookResult<T>;
function customHook<T>(key: string, options: CustomHookOptions<T> & { defaultValue?: undefined }): CustomHookResult<T | undefined>;
function customHook<T>(key: string, options: CustomHookOptions<T> = {}) {
  const defaultValue = options?.defaultValue
  const remoteValue = useFetch<T>(`/api/${key}`)

  const value = remoteValue ?? defaultValue;
  const setValue = (value: T) => { console.log(value) }

  return [value, setValue];
}

In this approach, three overloads are defined for the customHook function to alter the returned value based on whether options (specifically options.defaultValue) are supplied.

This allows you to:

const [foo, setFoo] = customHook<string>('foo', { defaultValue: 'something' }); // foo is a string
const [bar, setBar] = customHook<string>('bar'); // bar is a string | undefined
const [baz, setBaz] = customHook<string>('baz', {}); // baz is a string | undefined (no defaultValue provided despite options given).

Feel free to experiment with this further by using this TypeScript playground link.

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

Tips on designing unique field validation decorators in NestJS using GraphQL

I'm currently developing a NestJS API using apollo-server-express and have created an InputType for appointments as shown below: @InputType() export class AppointmentInput { @Field(of => String) @IsNotEmpty() name: string; @Field(o ...

Utilizing TypeScript in conjunction with Vue and the property decorator to assign default values to props

Hey there! I'm currently dealing with a property that looks like this, but encountering a type error when trying to translate text using i18n @Prop({ default: function() { return [ { > text: this.$t('wawi_id'), align: ...

Leveraging .tsx components within nested .tsx components in React Native

Currently, I am delving into the world of building apps using TypeScript in React Native. Coming from a background as a Swift developer, adjusting to JavaScript and TypeScript has been an interesting journey. An observation that stood out to me is the cha ...

Interactions between two components communicating through a method

I'm facing a peculiar issue with my code. I have component A and service B. I send an object from component A to service B, where it gets pushed into an array. Changes made to the data in the array are reflected back in the A component. Here's t ...

Exploring the Concepts of Union and Intersection Types in Typescript

I am trying to wrap my head around Union and Intersection types in TypeScript, and I've come across a case that's puzzling me. You can check it out on this Playground Link interface A { a: number; } interface B{ b: boolean; } type Un ...

No data is generated when choosing from the dropdown menu in mat-select

I have a select function where all options are selected, but the selected sections are not shown. When I remove the all select function, everything works fine. Can you help me find the error? Check out my work on Stackblitz Here is my code: Select <m ...

The power of Ionic 2 combined with the Web Audio API

I am currently developing an Ionic 2 application that requires access to the user's microphone. When working on a web platform, I would typically use the following code snippet to obtain microphone access. navigator.getUserMedia = (navigator['ge ...

The process of transferring information from a JSON API to TypeScript models

When working with my JSON API in my services, I need to pass the data to my models. What is the most efficient way to accomplish this task? Currently, I am following this process: import { Attachment } from '.'; export class Contact { id: nu ...

Decoding the Mystery: What Do the + and # Symbols Mean in Angular 4 Routes?

Check out the repository at https://github.com/AngularClass/angular-starter https://i.sstatic.net/ITi80.png I noticed the use of + and # for referencing within loadChildren and naming conventions in the folder... After consulting the Angular documentati ...

The "path" parameter must be a string data type in order to proceed. The value received is currently undefined

My current project is utilizing Angular 8 When I attempt to run 'ng build --prod', my project encounters errors. ERROR in The "path" argument must be of type string. Received type undefined The issue arose after adding "enableIvy": true to the ...

What is the process for consumers to provide constructor parameters in Angular 2?

Is it possible to modify the field of a component instance? Let's consider an example in test.component.ts: @Component({ selector: 'test', }) export class TestComponent { @Input() temp; temp2; constructor(arg) { ...

Is there a way to incorporate an SSL certificate into a JavaScript POST request?

I need to send a post request to an endpoint using a SSL certificate in either typescript or javascript. This is for a project that I am currently working on with Ionic 3. ...

The most secure method for retrieving User Id in AngularFire2

I'm currently facing a dilemma in determining the most secure method to obtain an authenticated user's uid using AngularFire2. There seem to be two viable approaches available, but I am uncertain about which one offers the best security measures ...

What is the best way to play AudioBuffer on an iPhone device?

When retrieving audio data from a stream, I encounter an issue with playing audio on iPhone Safari. The sound does not play unless I allow mediaDevice access for audio. Is there a way to play the audio without having to grant this permission? This is the ...

Ensuring a User has an Image in MySQL Using Angular 6

As part of my development process, I am working on creating a new user and sending their information along with an image to a MySQL database. The process involves sending a user object with form data through the following component.ts file: subscribeUser() ...

When using ngStyle to bind a variable, the binding variable will be null

Currently, I am attempting to utilize ngStyle to set the background image. <div class="artist-banner fluid-banner-wrapper" [ngStyle]="{'background-image': 'url(../imgs/banner/' + event?.category + '.jpg)' }"> The fun ...

Exploring Angular 8 Routing Testing within a Service

I am currently in the process of testing an Http Interception service that utilizes routing to redirect to another URL upon encountering an error response. All of my tests are passing smoothly since I am not specifically testing the routing functionality i ...

Integrating fresh components into a JSON structure

I've been attempting to insert a new element into my JSON, but I'm struggling to do it correctly. I've tried numerous approaches and am unsure of what might be causing the issue. INITIAL JSON INPUT { "UnitID":"1148", "UNIT":"202B", "Sp ...

The numerical value of zero in React Native TypeScript is being interpreted as NaN

When attempting to map an array in React Native (Android) and use its values or keys as data for return, I encountered an issue where the value 0 is read as NaN. This problem also arose when utilizing a typescript enum. The versions I am using are: typesc ...

The issue lies within typescript due to process.env.PORT being undefined

I am a newcomer to working with TypeScript. Even after importing and using the dotenv package, I am still encountering issues with getting undefined values. Do I need to declare an interface for the dotenv variables? import express,{Application} from &apo ...