Is it possible to create generic types for type predicate functions in TypeScript?

While attempting to create a function for checking generic types, I encountered an unusual error during my research. Despite searching on Google, I wasn't able to find much information, so now I'm curious if it's feasible to accomplish the following:

type Items<T = any> = { [item: string]: T }

type Cart<T = any> = {
  cart: Items<T>
}

type OptionalCart<T = any> = Partial<Cart<T>>

type SessionHasCart = <T extends Items = Items>(session: OptionalCart<T>) => session is Cart<T>

const sessionHasCart: SessionHasCart = (session) => {
  return (session as Cart).cart !== undefined
}

Check out the Playground here.

An error message that I keep encountering is:

Type '(session: Partial<Cart<Items<any>>>) => boolean' is not assignable to type 'SessionHasCart<Items<any>>'. Signature '(session: Partial<Cart<Items<any>>>): boolean' must be a type predicate.ts(2322)

Answer №1

Summary:

Absolutely, you have the ability to specify types for predicates with or without generics. However, it's important to note that only predicates can be assigned to these types, not just any function that returns a boolean.

Diving Deeper:

When it comes to assignment in TypeScript, a type cannot be narrowed down. A predicate function is considered more specific than a simple boolean-returning function.

Consider the following two types:

type SessionIsCart = (session: Session) => session is Cart;

type SessionTest = (session: Session) => boolean;

Every SessionIsCart is essentially a SessionTest, but the reverse is not true.

For instance:

function IsSessionExplicitlyNotACart(session: Session): boolean {
  // Differentiating between {} and {items: undefined}
  return 'items' in session && session.items === undefined;
}

While this function does return a boolean value, it does not serve as a predicate to determine if the session is of type Cart.

If you want your function to act as a predicate, you must explicitly define the signature in TypeScript to indicate that the returned boolean holds special significance:

const sessionHasCart: SessionHasCart = (session: Session): session is Cart => {
  return (session as Cart).cart !== undefined
}

As mentioned by @caTS, type predicates are within the boolean type category, similar to symbol constants falling under the symbol type and string literals fitting under the string type. While they are normal types, they possess unique characteristics.

Additionally, it's worth noting that a generic type guard can participate in inference effortlessly.

Here is an example showcasing the usage of type guards for functional filtering purposes. These functions are utilized extensively in my workplace, alongside other custom implementations I've developed.

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

Retrieving a nested type based on a particular condition while keeping track of its location

Given an object structure like the one below: type IObject = { id: string, path: string, children?: IObject[] } const tree = [ { id: 'obj1' as const, path: 'path1' as const, children: [ { id: &ap ...

What is the process for defining the root of a project in ESLint?

I've been working on a project using Next.js and Typescript. My imports look like this: import Component from "/components/Component/Component";, with the root directory being specified as /src. This setup works fine in Next.js, but ESLint k ...

Error: The JSX element's 'children' attribute is expected to have a single child of type 'ReactNode', but it received multiple children

Currently, I am working on a webpage using Next JS. During development, everything works smoothly without any errors. However, when I build the project for the production environment, I encounter the following error message: Type error: The 'child ...

Error: The window object is not defined in NextJS

I've encountered an issue while trying to build the app for production. The error message states: ReferenceError: window is not defined. I'm struggling to find a solution. FullCode: const [windowSize, setWindowSize] = useState<WindowInfo>( ...

How can resolvers in GraphQL optimize data fetching based on necessity?

I am working with two unique GraphQL types: type Author { id: String! name: String! } type Book { id: String! author: Author! name: String! } Within my database structure, there exists a foreign key inside the books table: table authors (pseu ...

In an Electron-React-Typescript-Webpack application, it is important to note that the target is not a DOM

Rendering seems to be working fine for the mainWindow. webpack.config.js : var renderer_config = { mode: isEnvProduction ? 'production' : 'development', entry: { app: './src/app/index.tsx', //app_A: './src/a ...

Querying with a TypeORM many-to-many relationship

Entity Program: export abstract class ProgramBase { @Column('varchar') programName: string; @Column('text') programDescription: string; @Column({ type: 'boolean', default: false }) isDeleted: boolean; @Column( ...

Angular 10 and Typescript: Variables assigned within the change event become undefined

In my code, I initialize an Algolia input and set an onchange event to it. This initialization takes place in a service. algolia_data; random_var; this.http.post<any>('APIENDPOINT', formData).subscribe(data => { instance = places({ ...

I encountered an error with Firebase when attempting to run functions on my local machine

Encountering a Firebase error when running the function locally using emulator in CLI $ firebase emulators:start --only functions Initiating emulators: ["functions"] functions: Using node@8 from host. functions: Emulator started at http://localhost:50 ...

What is the best way to delay a recursive JavaScript function for 3 seconds?

Before writing this post, I have already come across the following questions: how-to-pause-a-settimeout-call how-to-pause-a-settimeout-function how-to-pause-a-function-in-javascript delay-running-a-function-for-3-seconds Question The below code snipp ...

Arrange an array of objects by making a nested API call in Angular

My task involves sorting an array of objects based on the response from the first API call in ascending order. The initial API call returns a list of arrays which will be used for the subsequent API call. The first API call fetches something like this: [0 ...

Steps for producing an interactive Material UI Icon Atom in StoryBook and React TypeScript

I have integrated StoryBook with React, TypeScript, and Material UI. My goal is to create a dynamic Icon Atom component that can render different icons based on the props passed to it. Currently, I am facing issues with my current approach. Specifically: ...

What could be causing my React Redux state to not trigger a re-render?

Having trouble with my redux state not triggering a re-render when using a selector. I'm new to react-redux and typescript, and despite following advice online about returning a new object from the reducer, my object is still not re-rendering even tho ...

Steps to include a Target property in a freshly created MouseEvent

Trying to dispatch a contextMenu event, I've noticed that in the MouseEvent interface for TypeScript, the target property is missing, even though it is documented in the contextMenu documentation. Here's my TypeScript snippet: const emulatedMou ...

Error: Expected an expression but found a parsing error in the eslint code

interface Address { street: string, } export const getAddress = (address: Address | null) : string => address?.street ? `${address?.street}` : '0000 Default Dr'; Why am I receiving the error message Parsing error: Expression expected ...

Steps for appending a string to a variable

Currently working on creating a price configurator for a new lighting system within homes using Angular 7. Instead of using TypeScript and sass, I'm coding it in plain JavaScript. Page 1: The user will choose between a new building or an existing one ...

"From time to time, reimport React when saving to ensure all necessary imports are

When working with TypeScript in *.tsx files, particularly when copying code around, I frequently encounter the issue of an additional import line being added. This can be seen below: import React from "react"; // ? On Save "editor ...

What is a way to construct an object without resorting to casts or manually declaring variables for each field?

Click here for a hands-on example. Take a look at the code snippet below: export type BigType = { foo: number; bar?: number; baz: number; qux?: string[]; }; function BuildBigType(params: string[]) { // Here's what I'd like to do: ...

Inquiring about using React typescript useRef function for improved performance

Creating a reusable hook to manipulate the DOM is the objective. Sample Code: import { useEffect, useRef } from 'react'; function useFocus() { const domRef = useRef<HTMLElement | null>(null); useEffect(() => { domRef.current?.f ...

How to conditionally import various modules in Next.js based on the environment

Having two modules 'web,ts' and 'node.ts' that share similar interfaces can be challenging. The former is designed to operate on the client side and edge environment, while the latter depends on node:crypto. To simplify this setup, I a ...