3

I want to write a typescript wrapper to do a type safe fetch with Typescript. I've been googling around and the best source I've found is a this thread from 2016. It seemed to help the people in 2016, but now I don't get the snippet to work.

The following makes the TS Linter complain ([ts] Expected 0 type arguments, but got 1.):

response.json<T>()

So according to according to this thread I changed it to the following

data => data as T

Neither of the options above works, as both calls returns undefined. If a source such as this is used, I expect to be able to use {name: string, username: string}, like so:

api<{ name: string; username: string }>('https://jsonplaceholder.typicode.com/users')

The following then clause:

 .then(({ name, username })

A thorough walk through on how to create a wrapper function for doing type safe fetch calls would be appreciated.

EDIT

Adding a more complete snippet as requested

api<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);
    })
    .catch((error: Error) => {
      throw error;
    });
}

// consumer
this.api<{ name: string; username: string }>("https://jsonplaceholder.typicode.com/users/")
  .then(({ name, username }) => {
    console.log(name, username);
  })
  .catch(error => {
    console.error(error);
  });
4
  • How does your code look like? Commented Oct 9, 2018 at 9:49
  • @MuratKaragöz, done that now Commented Oct 9, 2018 at 10:03
  • Looks fine which ts version do you use? Commented Oct 9, 2018 at 10:09
  • @MuratKaragöz, version 3.0.3 Commented Oct 9, 2018 at 10:10

2 Answers 2

3

The Typescript part is fine. Using as T in the json method should work fine.

The problem is your json contains an array, not a single object. So the rest of the code would work if you wrote return response.json().then(data => data[0]); selecting just one element.

You probably want the whole array though, in which case you need to change the consumer to pass an array of the expected type:

this.api<Array<{ name: string; username: string }>>("https://jsonplaceholder.typicode.com/users/")
    .then(data  => {
       data.forEach(({ name, username }) => console.log(name, username))
    })
    .catch(error => {
      console.error(error);
    });
Sign up to request clarification or add additional context in comments.

Comments

0

Backend and frontend get compiled separately, so you can't typecheck what your frontend/backend gets, but you can use shared between frontend and backend types/interfaces and do like this:

const res = await fetchJSON<ApiReqLogin, ApiResLogin>('api/login', { code })

ApiReqLogin - is a type/interface that for this request, ApiResLogin - for expected response.

On server you should make sure that resonse is of type ApiResLogin and on frontend that request is of type ApiReqLogin.

Fetch wrapper:

const fetchJSON = <Req, Res>(link: string, body: Req): Promise<{ data: Res; mes: false } | { data: false; mes: string }> => {
  return new Promise(resolve => {
    fetch(link, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    })
      .then(res => res.json())
      .then((data: Res) => {
        resolve({ data, mes: false })
      })
      .catch(err => {
        resolve({ data: false, mes: 'error' })
      })
  })
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.