Enhance your TypeScript skills by leveraging types on the call() method in redux-saga

Is there a way to specify the types of a function using call()?

Consider this function:

export function apiFetch<T>(url: string): Promise<T> {
    return fetch(url).then(response => 
        {
            if (!response.ok) throw new Error(response.statusText)
            return response.json().then(data => data as T);
        }
    )  
}

This function is typically used like this:

let resp = await apiFetch<ServerResponse>("http://localhost:51317/Task");

When used in the above manner, resp correctly retains its string type. This enables Intellisense to offer all attributes of the ServerResponse interface.

However, calling this function inside a worker from redux-saga poses a challenge because async functions are not allowed:

function* refreshTaskSaga():any {
    yield takeEvery("TASK_REFRESH", workerRefreshTaskSaga);
}


function* workerRefreshTaskSaga() {
  //The function needs to be called here
}

I attempted to call it using yield + call, following redux-saga documentation:

a) let resp = yield call(apiFetch, "http://localhost:51317/Task");
b) let resp = yield call(apiFetch<ServerResponse>, "http://localhost:51317/Task");

The first option executes the function correctly but assigns any type to resp. The second option results in an exception being thrown.

No overload matches this call.
  The last overload gave the following error.
    Argument of type 'boolean' is not assignable to parameter of type '{ context: unknown; fn: (this: unknown, ...args: any[]) => any; }'.ts(2769)
effects.d.ts(499, 17): The last overload is declared here.

Can anyone provide insight into the correct syntax to maintain types when calling this function?

Answer №1

Regrettably, the left side of a yield statement always has the type any. This is due to the fact that a generator function can potentially be resumed with any value. While Redux saga behaves predictably when running generators, there is nothing preventing someone from executing other code that navigates through your saga and provides values unrelated to what was yielded.

const iterator = workerRefreshTaskSaga();
iterator.next();
// In this case, you might have been expecting a ServerResponse, but instead you're getting a string.
iterator.next('hamburger'); 

Only if you can assume that Redux saga is the one running your generator can you accurately predict the types. Unfortunately, TypeScript lacks the capability to specify "assume this generator will be run by Redux saga (and all its implications)".

Therefore, you must manually add the types yourself. For instance:

const resp: ServerResponse = yield call(apiFetch, 'url');

This means it's your responsibility to ensure the correct types are applied. TypeScript will only recognize it as an any, trusting you with defining the type. It can verify that subsequent code interacts appropriately with a ServerResponse, but cannot identify if it is not actually a ServerResponse.

One approach I often use for added type safety is utilizing ReturnType like so:

const output: ReturnType<typeof someFunction> = yield call(someFunction);

While it remains my duty to confirm that

ReturnType<typeof someFunction>
is accurate, if someone alters the implementation of someFunction causing it to return something different, output's type will be automatically updated accordingly.

Answer №2

After checking out the TypeScript 3.6 release notes, I came across a feature where the yield type can be set as the third argument in Generator Type.

import { AnyAction } from "redux";
import { call, put, fork, takeLatest, StrictEffect } from "redux-saga/effects";
import { apiRequest } from "api/requests";
import { setAuthenticationLoader, setLoginError, setToken } from "./actions";
import { sagaTypes } from "./types";
import { LoginResponse } from "api/requests/authentication";

export function* requestLogin(
  action: AnyAction
): Generator<StrictEffect, any, LoginResponse> {
  const setError = (err?: any) => put(setLoginError(err));
  yield put(setAuthenticationLoader(true));
  yield setError();
  try {
    const data = yield call(apiRequest.authentication.login, action.payload);
    if (!data.token) setError(data);
    else yield put(setToken(data.token));
  } catch (err) {
    yield setError(err);
  } finally {
    yield put(setAuthenticationLoader(false));
  }
}

function* watchLoginRequest() {
  yield takeLatest(sagaTypes.REQUEST_LOGIN, requestLogin);
}

export const authenticationSagas = [fork(watchLoginRequest)];

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

The user interface design transforms as a PDF file is being generated through html2pdf

I am experiencing an unusual problem while using html2pdf to convert an HTML page to a PDF file and download it. The conversion process is successful and the PDF file is downloaded without any issues. However, when I click on a button to generate the file, ...

@ngrx effects ensure switchmap does not stop on error

Throughout the sign up process, I make 3 http calls: signing up with an auth provider, creating an account within the API, and then logging in. If the signup with the auth provider fails (e.g. due to an existing account), the process still tries to create ...

TypeScript does not pay attention to additional properties in the return type of the setState function

I'm completely lost here. I don't understand why typescript allows me to return anything in the setFormValidation call as long as I include the prevState spread in the return object. It seems to ignore all other properties that I return in the ob ...

invoke the next function a different privateFunction within rxjs

I'm trying to figure out how to pass the resetPassword data to the _confirmToUnlock method in Typescript/RxJS. Here is my subscribe method: public invokeUnlockModal() { let resetPassword = { userName: this.user?.userName}; //i need to send this ...

React and Typescript Multimap Approach

I'm a beginner in TypeScript and I am struggling to understand how to create a multimap. The code I have is shown below. My goal is to loop through the itemArray and organize the items based on their date values. I want to use the date as the key for ...

Can a type be created that resolves to either of two specific types?

If I have multiple functions that return either a number or a date, is there a way to define a type that encompasses both? For example, instead of: foo1(): number | Date {} foo2(): number | Date {} Can we do something like this: foo1(): NumberOrDate {} f ...

Exploring the synergies between Typescript unions and primitive data types in

Given the scenario presented interface fooInterface { bar: any; } function(value: fooInterface | string) { value.bar } An issue arises with the message: Property 'bar' does not exist on type '(fooInterface | string)' I seem ...

Using TypeScript with Node.js: the module is declaring a component locally, but it is not being exported

Within my nodeJS application, I have organized a models and seeders folder. One of the files within this structure is address.model.ts where I have defined the following schema: export {}; const mongoose = require('mongoose'); const addressS ...

Generating an array of elements from a massive disorganized object

I am facing a challenge in TypeScript where I need to convert poorly formatted data from an API into an array of objects. The data is currently structured as one large object, which poses a problem. Here is a snippet of the data: Your data here... The go ...

What is the best approach to transpiling TypeScript aliased paths to JavaScript?

I am currently facing an issue with my TypeScript project where I need to transpile it into executable JavaScript while using path aliases for my NPM package development. One specific scenario involves importing a method from the lib directory without spe ...

When attempting to create a fresh NestJS module, a message pops up stating: "An error occurred while

Currently running MacOS Monterey with the M1 chip as my OS. I installed NestJS CLI using the command: sudo npm install -g @nestjs/cli When creating a new Nest project with nest new message, everything goes smoothly. However, when attempting to generate a ...

Troubleshooting the issue with generateStaticParams() in NextJs/TypeScript

My NextJs app has a products page that should render dynamic routes statically using generateStaticParams(). However, this functionality does not work as expected. When I run "npm run build," it only generates 3 static pages instead of the expected number. ...

Tips on showcasing an array as a matrix with a neat line property

I am currently developing an application using TypeScript, and utilizing a JSON array structured like this: data = [{"name":"dog", "line":1}, {"name":"cet", "line":1}, ...

Tips for updating the checkbox state while iterating through the state data

In my component, I have the ability to select multiple checkboxes. When a checkbox is selected, a corresponding chip is generated to visually represent the selection. Each chip has a remove handler that should unselect the checkbox it represents. However, ...

Manipulating Angular and Typescript to utilize the method's parameter value as a JavaScript object's value

I am currently working with Ionic, Angular, and Typescript, attempting to dynamically set the value of a location based on the parameter passed from a method. Here is the relevant code snippet: async fileWrite(location) { try { const result = a ...

Is it possible to invoke Cucumber stepDefinitions from a separate project at the same directory level?

Currently, I have a project called integration_test that includes all test projects utilizing cucumberjs, typescript, and nodejs. Project1 contains the login implementation, and I would like to use this implementation in Scenarios from Project2 and Projec ...

Tips for ensuring proper dependency regulations in javascript/typescript/webpack

In essence, I am in search of a method to limit dependencies, similar to how one would manage different projects (libraries) in Java or C#. Think of it as friend or internal access modifiers. I'm considering various approaches to accomplish this (suc ...

Misunderstanding between Typescript and ElasticSearch Node Client

Working with: NodeJS v16.16.0 "@elastic/elasticsearch": "8.7.0", I am tasked with creating a function that can handle various bulk operations in NodeJS using Elasticsearch. The main objective is to ensure that the input for this funct ...

Utilizing Google Sheets as a secure, read-only database for Angular applications without the need to make the sheet accessible to the

Seeking a way to utilize Google Sheets document as a read-only database for my Angular application, I have attempted various methods. However, the challenge with all these approaches is that they necessitate public sharing of the Sheet (accessible to anyon ...

Angular Http Promise is not returning the expected value

Struggling to update my component property with an HTTP result, but encountering issues. Thank you for your assistance! (currently using a static mock object) Class - Object export class Gallery { name: string; } Service import { Injectable } from ...