GithubHelp home page GithubHelp logo

Comments (7)

ivan-kleshnin avatar ivan-kleshnin commented on June 15, 2024 1

@toteto yeah, familiar situation šŸ˜… . IMO it's easier to remap your understanding of "errors" than to fight with tooling.

onError corresponds to code/client error.
onSuccess corresponds to the absence of the above errors.

From the client point of view 404, 500, etc. response status codes are not errors but expected scenarios. So you handle them in the same branch as 200.

If you think about it like this, then everything behaves as expected.

from ts-rest.

Gabrola avatar Gabrola commented on June 15, 2024 1

@ivan-kleshnin there are .query() and .mutation() functions exposed that will fetch directly. So you can do await client.getPost.query()

from ts-rest.

Gabrola avatar Gabrola commented on June 15, 2024

Thank you for your proposal! Pretty well thought out, but you've overlooked a major point, which is why @ts-rest/react-query exists in the first place, and which solves this entire use-case, which I'll elaborate on later.

The thing about ts-rest is that we try to make its usage as convenient as possible while being as unopinionated as much as possible. I believe we have struck this right balance.

Our core client uses fetch which does not throw on error response codes, therefore we do not. fetch is the standard now on the web, so we are sticking to its conventions. It's not really in our philosophy to be creating custom clients ourselves with elaborate error handling or features such as middleware, or auth or whatever since there are many different use-cases that we cannot cover all, or some, without being opinionated.

A such, we have provided functionality in order to allow everyone to customize the "client" to behave however they want it, or even swap out fetch entirely if they want to. This is the only way we can guarantee that ts-rest works for everyone.

In your case you can just simply wrap the tsRestFetcher and do the error handling in a custom fetcher.

const client = initQueryClient(postsApi, {
  baseUrl: 'http://localhost:5003',
  baseHeaders: {},
  api: async (args) => {
    const response = await tsRestFetchApi(args);
    if (!String(result.status).startsWith('2')) {
      throw response;
      // or wrap with your own error, whatever
    }
    return response;
  },
});

Now for react query, @ts-rest/react-query mainly exists as a typing wrapper rather than providing extra runtime functionality. If you opt to use just react-query then you will need to do lots of manual typing work regardless of if you use your own custom fetcher or if we theoretically used a newly proposed ts-rest client.

const { mutate } = useMutation<
  ClientInferResponses<typeof contract.addPost, SuccessfulHttpStatusCode, 'force'>,
  ClientInferResponses<typeof contract.addPost, ErrorHttpStatusCode, 'ignore'> // or whatever you wrap the error with
>({
  mutationFn: (newPost) => client.addPost(newPost),
  onSuccess: (response) => {
  },
  onError: (error) => {
    // or type guard here instead of using the type generics
  },
});

For the error typing you will still have to either manually type useMutation or useQuery's second generic or type guard the error every single time. It will get very repetitive.

Anyway it seems that your problem with using @ts-rest/react-query is something very specific and it's hard to justify building extra functionality that will take development effort to maintain just to cover a very specific use-case.

However, the types and functions to assist with building this functionality yourself is already there.

from ts-rest.

toteto avatar toteto commented on June 15, 2024

@Gabrola
Thank you for your prompt response, and I apologize for the delay in my reply. I missed the notification in my inbox.

Regarding my usage of @ts-rest/react-query, I initially misunderstood an implementation detail: this client actually only allows 2xx responses in the onSuccess callback. This behavior aligns with my requirements for a standalone client.

While @ts-rest/react-query generally meets my needs, there are specific cases where I encounter challenges. For instance, in a single useMutation.mutationFn, I sometimes need to perform multiple steps during the mutation process. Additionally, the error type in orError is currently incorrect; it should handle any thrown errors, not just error status responses.

Putting aside my personal preferences and specific use case, I wonder if there's a way to engage the community and discuss the desired behavior of the core client.

One observation that leads me to believe that throwing errors might be more desirable is the implementation in @ts-rest/react-query. Furthermore, many developers prefer the API of axios over fetch.

Iā€™d appreciate any insights or thoughts on PR #522 whenever you have a moment.

As a temporary solution, I've created a utility function in my code that can be chained to the response promise. This utility returns the success response but throws an error response when necessary:

export function successBody<TStatus extends SuccessfulHttpStatusCode, TRes extends Res>(
    response: TRes,
    status?: TStatus,
): (TRes & { status: TStatus extends undefined ? SuccessfulHttpStatusCode : TStatus })['body'] {
    if (status != null ? response.status === status : response.status >= 200 && response.status < 300)
        return response.body;
    throw new TsRestError(response);
}

const { status, body: posts } = client.getPosts({...}).then(successBody);
// status is 2xx, posts is of type Post[]
// Wrap in a try-catch block to handle errors with status codes outside the 2xx range

However, I'm not particularly fond of this approach, as I need to remember to include it in every method call.

from ts-rest.

Gabrola avatar Gabrola commented on June 15, 2024

@toteto I left you some comments on the PR. However, I still don't believe much in the utility of throwing on fetch except to trigger an error in tanstack query. Otherwise, it's pretty much the same amount of code; whether you wrap in try/catch, or if you just create an if condition to check for the success status. This leads me to believe, that this change only exists to cover the use-case of wanting to use tanstack query but not wanting to use the ts-rest wrappers.

Even if you were to manually use tanstack query, all of the new types in the regular client are still useless because you still have to manually type the error object, and since the error type is the second generic on useQuery, etc. you will still have to manually pass the success type and error type every single time you use useQuery or useMutation.

Regarding not using @ts-rest/react-query, if you need to perform any steps before running a mutation, then simply just run these steps before calling mutate.

I cannot justify including code for a feature, just to cover a very specific use-case that has not been requested by anyone else yet.

from ts-rest.

Gabrola avatar Gabrola commented on June 15, 2024

And yes, regarding the incorrectly typed error object in @ts-reset/react-query, I am aware of that, however it is too late to fix now without a breaking change. So we'll be fixing it in v4

from ts-rest.

ivan-kleshnin avatar ivan-kleshnin commented on June 15, 2024

@Gabrola is it possible to use a client created with initQueryClient for direct (hook-less) fetches? I mean:

const client = initQueryClient(...)

client.getPost.useQuery() // -- does work
// then, at some place in code, I need raw `client.getPost()`
await client.getPost() // -- is not a function, any alternative syntax?

Should I create 2 clients: one for higher- and another for lower-level API handling?
Will they have a single shared cache in that case?

I use React-Query but it's often necessary to make direct calls, without useQuery wrapper ā€“ be it a server-side prefetchQuery() or another necessity.

from ts-rest.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.