Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: How to correctly type response.data on front-end side based on success of the request?

Consider this context: An API auth route which I don't have access to view or edit the code returns

interface AuthSuccess {
  token: string
  user: object
}

on response.data if email and password are correct, but returns

interface AuthError {
  error: string
}

on error.response.data if they aren't correct or aren't passed in the body.

On my front-end, built in NextJS with axios to make requests, I've created the following function to call this route:

const auth = ({email, password}) =>
  api // api is the return of axios.create()
    .post(url, {email, password})
    .then((result) => ({
      success: true,
      data: response.data // here it it AuthSuccess
    }))
    .catch((error) => ({
      success: false,
      data: error.response?.data // here it is AuthError
    }))

However, when I call this function like this

const res = await auth(data)

if (res.success) {
  // here I expect data to be AuthSuccess type
} else {
  // here I expect data to be AuthError type
}

Even if I'm verifying res.success, inside the if block Typescript acts like data can have both types, which it can't, obviously.

I've tried 2 solutions so far:

  1. Setting auth type as (arg: AuthProps): Promise<AuthSuccess | AuthError>, which creates the problem with the if block

  2. Creating another type, conditionally:

interface AuthResult<T extends boolean> {
  success: T
  data: T extends true ? AuthSuccess : AuthError
}

And setting auth to be (arg: AuthProps): Promise<AuthResult>, to which TS complains about not passing the generic, and at the if block everything becomes any.

So, how can I type this function correctly? Or how can I refactor it to stop the problem? Ty in advance

like image 998
Kyomen Avatar asked Oct 17 '25 00:10

Kyomen


1 Answers

You can use a union type:

interface SuccessResponse {
  success: true;
  data: {
    token: string;
    user: object;
  };
}

interface ErrorResponse {
  success: false;
  data: {
    error: string;
  };
}

type AuthResponse = SuccessResponse | ErrorResponse;

const test = (res: AuthResponse) => {
  if (res.success) {
    return res.data; // { token: string; user: object; }
  } else {
    return res.data; // { error: string; }
  }
}

Typescript playground


Or a function that returns a type predicate:

interface SuccessResponse {
  success: true;
  data: {
    token: string;
    user: object;
  };
}

interface ErrorResponse {
  success: false;
  data: {
    error: string;
  };
}

const isSuccessResponse = (res: SuccessResponse | ErrorResponse): res is SuccessResponse => {
  return res.success;
}

const test = (res: SuccessResponse | ErrorResponse) => {
  if (isSuccessResponse(res)) {
    return res.data; // { token: string; user: object; }
  } else {
    return res.data; // { error: string; }
  }
}

Typescript playground

like image 75
Anton Podolsky Avatar answered Oct 19 '25 16:10

Anton Podolsky