A special function designed to accept and return a specific type as its parameter and return value

I am attempting to develop a function that encapsulates a function with either the type GetStaticProps or GetServerSideProps, and returns a function of the same type wrapping the input function.

The goal is for the wrapper to have knowledge of what it is wrapping, and I believe this can be achieved using generics.

How do I correct the example below? The desired outcome is to restrict passing only functions of type GetStaticProps or GetServerSideProps, allowing TypeScript (and my IDE) to understand what has been passed wherever this function is utilized.

export type GetGenericProps = GetStaticProps | GetServerSideProps;

export function handleGetPagePropsErrors<T extends GetGenericProps>(
  wrappedHandler: T,
): T {
  return async (context) => {
    try {
      return await wrappedHandler(context);
    } catch (err) {
      if (err instanceof AppError) {
        return {
          props: {
            error: {
              message: err.message,
              type: err.type,
            }
          }
        };
      } else {
        throw err; // allow Next.js to handle it
      }
    }
  };
}

If I utilize this function as shown below, I expect context to be recognized along with the types of req, res, and params matching those of GetServerSideProps.

export const getServerSideProps: GetServerSideProps = handleGetPagePropsErrors(
  async ({ req, res, params }) => {
    if (Math.random() > 0.5) {
      throw new AppError(
        ErrorType.BAD_THINGS_HAPPEN, 
        "Sometimes code just doesn't work, dude"
      );
    }

    return {
      props: {
        foo: 'bar'
      },
    };
  },
);

The AppError is simply a basic class extending Error that includes an error type from an enum.

class AppError extends Error {
  type: ErrorType;

  constructor(type: ErrorType, message: string) {
    super(message);
    this.type = type;
  }
}

enum ErrorType {
  BAD_THINGS_HAPPEN = 'BAD_THINGS_HAPPEN'
}

Answer №1

If you are confident that a value belongs to a specific type, but the compiler is unable to confirm and raises an error, you can utilize a type assertion to suppress its warning. (In some cases, when the asserted type is vastly different from what the compiler expects, an intermediate type assertion may be needed. So if foo as Bar doesn't work, you can resort to foo as any as Bar instead.)

In the context of your code, this would look like:

function handleGetPagePropsErrors<T extends GetGenericProps>(
  wrappedHandler: T,
): T {
  return (async (context: any) => {
    try {
      return await wrappedHandler(context);
    } catch (err) {
      if (err instanceof AppError) {
        return {
          props: {
            message: err.message,
            type: err.type,
          },
        };
      } else {
        throw err; // let Next.js handle it
      }
    }
  }) as T; // <-- assertion here
}

It's important to note that by utilizing type assertions, you take on the responsibility for ensuring type safety at runtime rather than compile time. If the type assertion turns out to be incorrect, runtime issues may arise without prior compiler warnings. Proceed with caution.

Furthermore, the generic type T extends GetGenericProps could be more specific than just one of the union elements. For instance, TypeScript allows for setting "expando" properties on functions, as illustrated below:

const gssp = async ({ req, res, params }: GetServerSidePropsContext) => {
  if (Math.random() > 0.5) {
    throw new AppError(
      ErrorType.BAD_THINGS_HAPPEN,
      "Sometimes code just doesn't work, dude"
    );
  }

  return {
    props: {
      foo: 'bar'
    },
  };
};
gssp.expandoProp = "oops";;

Therefore, while gssp is considered a GetServerSideProps, it additionally possesses a string-valued expandoProp property. This results in a type such as

GetServerSideProps & {expandoProp: string}
. Consequently, the output of handleGetPagePropsErrors(gssp) is expected to also have such a property:

const getServerSideProps = handleGetPagePropsErrors(gssp);
getServerSideProps.expandoProp.toUpperCase(); // permissible?
// No compiler error, potential runtime issue

However, in reality, the implementation of handleGetPagePropsErrors() does not exactly match the input type but a related type. Thus, technically, as T was misrepresented.

The likelihood of encountering such odd scenarios is slim in practice, yet awareness of these intricacies and judicious use of type assertions is advised.


Another approach involves embracing slightly easier-to-guarantee types (albeit shifting some of the type safety burden from compiler to coder) and configuring handleGetPagePropsErrors() as an overloaded function.

TypeScript permits declaration of multiple distinct call signatures for a function along with a single implementation catering to all signatures. While the compiler's validation is less stringent for such implementations, misrepresenting the return type remains plausible. Nevertheless, restricting the potential output types to solely GetStaticProps or

GetServerSideProps</code, instead of any generic subtype of <code>GetGenericProps
, becomes feasible.

Here's how you could implement this:

function handleGetPagePropsErrors(wrappedHandler: GetStaticProps): GetStaticProps;
function handleGetPagePropsErrors(wrappedHandler: GetServerSideProps): GetServerSideProps;
function handleGetPagePropsErrors(wrappedHandler: GetGenericProps): GetGenericProps {
  return (async (context: any) => {
    try {
      return await wrappedHandler(context);
    } catch (err) {
      if (err instanceof AppError) {
        return {
          props: {
            message: err.message,
            type: err.type,
          },
        };
      } else {
        throw err; // let Next.js handle it
      }
    }
  });
}

Consequently, the preceding issue concerning expando properties no longer persists as the function does not purport to provide something more specific than simply GetServerSideProps:

const getServerSideProps = handleGetPagePropsErrors(gssp);
// getServerSideProps: GetServerSideProps
getServerSideProps.expandoProp.toUpperCase(); // error!
// Property 'expandoProp' does not exist on type 'GetServerSideProps'

Playground link to code

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

Variable Scope is not defined in the TypeScript controller class of an AngularJS directive

I have implemented a custom directive to wrap ag grid like so: function MyDirective(): ng.IDirective { var directive = <ng.IDirective>{ restrict: "E", template: '<div style="width: 100%; height: 400px;" ag-grid="vm.agGrid ...

The takeUntil function will cancel an effect request if a relevant action has been dispatched before

When a user chooses an order in my scenario: selectOrder(orderId): void { this.store$.dispatch( selectOrder({orderId}) ); } The order UI component triggers an action to load all associated data: private fetchOrderOnSelectOrder(): void { this.sto ...

Passing specific props to child components based on their type in a React application using TypeScript

Sorry if this question has already been addressed somewhere else, but I couldn't seem to find a solution. I'm looking for a way to pass props conditionally to children components based on their type (i.e. component type). For example, consider ...

Using TypeScript to deserialize JSON into a Discriminated Union

Consider the following Typescript code snippet: class Excel { Password: string; Sheet: number; } class Csv { Separator: string; Encoding: string; } type FileType = Excel | Csv let input = '{"Separator": ",", "Encoding": "UTF-8"}&ap ...

Developing a constrained variable limited to specific values

Recently delving into Typescript - I am interested in creating a variable type that is restricted to specific values. For instance, I have an element where I want to adjust the width based on a "zoom" variable. I would like to define a variable type call ...

How can I achieve a result using a floating label in a .ts file?

I'm facing a simple issue that I can't seem to figure out. The problem is with a floating label in my HTML file, as shown below: <ion-list> <ion-item> <ion-label floating >Username</ion-la ...

What is the best location for the next font in the nx workspace?

Currently, I am working in a NX workspace with Next.js and several libraries. For my component library, I am using MUI, and I have also incorporated @next/font to load a Google font. In order to maintain consistency across my application, I created a libr ...

Guide to configure Validator to reject the selection of the first index option in Angular 2

When using a select option, it should be set up like: <div class="form-group row" [ngClass]="{'has-error': (!form.controls['blockFirstIndex'].valid && form.controls['blockFirstIndex'].touched), 'has-success&ap ...

Creating a registration and authentication system using firebase

When using Google authentication with Firebase, is the Signup and Login logic the same? I am creating a page for user authentication but I'm unsure if I should have separate pages for signing up and logging in. This confusion arises from the fact that ...

The Angular 2 router UMD file, router.umd.js, was not found

Trying to run an Angular 2 project and implement @angular/router is proving to be a bit challenging. Everything seems to be working fine, until the moment I attempt: import { provideRouter, RouterConfig } from '@angular/router'; As it tries to ...

All components load successfully except for the worker in Webpack

I've been working on configuring webpack to bundle my react project. I successfully set up the webpack_public_path variable and all modules are loading correctly, except for the worker. const path = require('path'); const MonacoWebpackPlugi ...

Thumbnail for Reddit link not displaying due to dynamic OG:image issue

I am currently working on a NextJS app that allows users to share links to Reddit. The issue I am facing is that the link preview in Reddit always shows the same thumbnail image, regardless of the shared link. Interestingly, this problem does not occur whe ...

Automatically signout of NextAuth.js if the Apollo GraphQL token is found to be invalid or has expired

Is there a recommended method for clearing the NextAuth.js session when encountering a 401 error in the Apollo GraphQL backend due to an expired or invalid token? I have considered using errorLink and signout, but I am aware that signout cannot be utilize ...

Restrictive discriminated union via function argument

I am in possession of a shop that organizes a variety of types based on their IDs interface Dog { type: "dog"; woofs: string; } interface Cat { type: "cat"; meows: string; } type Pet = Dog | Cat; type AnimalState = Record<string, Pet ...

Is it possible to access a class with protected/private fields written in TypeScript from outside the class in JavaScript?

Currently, I am delving into TypeScript classes (though my experience with OOP is limited). The following code snippet is extracted from the chapter on classes in https://www.typescriptlang.org/docs/handbook/classes.html Here's the issue at hand: I ...

Angular background image not displayed

After searching extensively, I came across many examples that didn't work for me. I'm not sure what I'm doing wrong and I need assistance! I noticed that I can see the image if I use the <img> tag, but not when I try to set it as a ba ...

Subscription date is activated when a different part of the state changes in ngrx

Within my state, I have properties named start and end which store dates. Whenever any other part of the state is modified, the subscription for these start and end dates is triggered. Here is the subscription implementation: this.subs.sink = this.store ...

Mastering Typescript lookup types - effectively limit the properties included in a merge operation with the Partial type

Exploring lookup types, I'm interested in creating a safe-merge utility function that can update an entity of type T with a subset of keys from another object. The objective is to leverage the TypeScript compiler to catch any misspelled properties or ...

Typescript raises a error notification regarding the absence of a semicolon when importing a JSON module

Attempting to import a local JSON object into my Vuex store using const tree = import('@/articles/tree.json');. The setting "resolveJsonModule": true, has been enabled in my tsconfig.json and it loads successfully, however NPM is flooding the out ...

The Next.js Hydration process has encountered a failure due to a discrepancy between the initial UI initialization and the server-rendered content

When I try to pull out the mobilesidebar from my code, it shows up as white with no issues. I receive the following message: The exact error message I am seeing Unhandled Runtime Error Error: Hydration failed because the initial UI does not match what was ...