Giter Site home page Giter Site logo

zodios-react's Introduction

Zodios React

Zodios logo

React hooks for zodios backed by react-query

langue typescript npm GitHub GitHub Workflow Status

Install

> npm install @zodios/react

or

> yarn add @zodios/react

Usage

Zodios comes with a Query and Mutation hook helper.
It's a thin wrapper around React-Query but with zodios auto completion.

Zodios query hook also returns an invalidation helper to allow you to reset react query cache easily

import React from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import { Zodios, asApi } from "@zodios/core";
import { ZodiosHooks } from "@zodios/react";
import { z } from "zod";

// you can define schema before declaring the API to get back the type
const userSchema = z
  .object({
    id: z.number(),
    name: z.string(),
  })
  .required();

const createUserSchema = z
  .object({
    name: z.string(),
  })
  .required();

const usersSchema = z.array(userSchema);

// you can then get back the types
type User = z.infer<typeof userSchema>;
type Users = z.infer<typeof usersSchema>;

const api = asApi([
  {
    method: "get",
    path: "/users",
    description: "Get all users",
    parameters: [
      {
        name: "q",
        type: "Query",
        schema: z.string(),
      },
      {
        name: "page",
        type: "Query",
        schema: z.string().optional(),
      },
    ],
    response: usersSchema,
  },
  {
    method: "get",
    path: "/users/:id",
    description: "Get a user",
    response: userSchema,
  },
  {
    method: "post",
    path: "/users",
    description: "Create a user",
    parameters: [
      {
        name: "body",
        type: "Body",
        schema: createUserSchema,
      },
    ],
    response: userSchema,
  },
]);
const baseUrl = "https://jsonplaceholder.typicode.com";

const zodios = new Zodios(baseUrl, api);
const zodiosHooks = new ZodiosHooks("jsonplaceholder", zodios);

const Users = () => {
  const {
    data: users,
    isLoading,
    error,
    invalidate: invalidateUsers, // zodios also provides invalidation helpers
  } = zodiosHooks.useQuery("/users");
  const { mutate } = zodiosHooks.useMutation("post", "/users", undefined, {
    onSuccess: () => invalidateUsers(),
  });

  return (
    <>
      <h1>Users</h1>
      <button onClick={() => mutate({ name: "john doe" })}>add user</button>
      {isLoading && <div>Loading...</div>}
      {error && <div>Error: {(error as Error).message}</div>}
      {users && (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </>
  );
};

// on another file
const queryClient = new QueryClient();

export const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <Users />
    </QueryClientProvider>
  );
};

zodios-react's People

Contributors

dependabot[bot] avatar ecyrbe avatar renovate-bot avatar renovate[bot] avatar vanpav avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

zodios-react's Issues

"select" and "enabled" options doesn't seem to work

Hi, first time that I try this lib, I used it with the two options that I use without any issues with react-query, "select" and "enabled".
But setting enabled to false, or setting a selected to change the returned data doesn't work, is it a known issue ? Is there a workaround ?

image

Better way to access generated keys

Currently, the only way to get the key of an particular query is by using the provided useQuery hook, but it would be more useful to have an way to access keys both inside and outside of components. The class could expose an getKeyByAlias or getKeyByPath to allow key access outside of components. A good use case for that is integrating with React Router's loaders and actions, since they are created outside components.

import { Zodios } from '@zodios/core';
import { ZodiosHooks } from '@zodios/react';

const api = new Zodios(
    'https://jsonplaceholder.typicode.com',
    [{
        path: 'user/:id'
        alias: 'getUser',
        ...
    }]
)

const hooks = new ZodiosHooks('users', ZodiosAPI);
export const loader = async ({ params }) => {
  const queryKey = hooks.getKeyByAlias('getUser', { params: { id: params.id } });
  return (
    queryClient.getQueryData(queryKey) ??
    (await queryClient.fetchQuery(api.getUser({ params: { id: params.id } })))
  )
}

`asApi` is deprecated, 'react-query' isn't latest version

Noticed that the asApi function from @zodios/core is gone as of v10 of @zodios/core.
The example in the README still calls asApi.

Also, the 'react-query' import is v3, should update to @tanstack/react-query@^4.29.4

Could even update to @tanstack/[email protected]: https://github.com/TanStack/query/discussions/4252#discussioncomment-5641490

Passing `AbortSignal` down to `axios` request config

As it currently stands, it doesn't seem like @zodios/react passes down signal provided by react-query's query function down to axios request config.

react-query doesn't enforce an opinion on whether or not you should use abort signals while handling asynchronous state, but given the fact that this library works exclusively with HTTP requests I think it makes sense to pass signal as a sensible default or at least provide a way for users to specify whether or not queries should be cancelled.

See this: https://tanstack.com/query/v4/docs/guides/query-cancellation.

Automatically add `mutationKey` to mutations

First of all thanks for developing this awesome library, it has been a joy to work with it. I found a possible way to improve it but I'm not sure if you already considered the option.

Today I was trying to use TanStack Query's useIsMutating like this:

const count = useIsMutating({
  mutationKey: apiHooks.getKeyByAlias('updateCart', {
    params: { id: cartId },
  }),
});

but it was always returning 0, I had to do some digging to realize that I needed to explicitly pass the mutationKey option to the useUpdateCart hook:

apiHooks.useUpdateCart(
  { params: { id: cartId } },
  { 
    mutationKey: apiHooks.getKeyByAlias('updateCart', {
      params: { id: cartId },
    })
  }
)

So I think it will be good to add the mutationKey automatically, let me know if it makes sense and I can create a PR for it.

Help : infinite loop when consuming hook

Hi,

First of all, thanks for this library. It's very promising, I already love it.

I'm getting a render infinite loop; but I suspect this is because I'm doing something wrong that I cannot nail down.
Here's the setup :

package.json

    "@tanstack/react-query": "^4.36.1",
    "@zodios/core": "^10.9.6",
    "@zodios/react": "^10.4.5",
    "axios": "^1.6.2",

api-def.ts

import { makeApi } from '@zodios/core'

export const getWord = makeEndpoint({
  method: 'get',
  path: '/word',
  response: daywordDTO.nullable(), // Some DTO zod schema, skipped for clarity
  alias: 'getWord',
  description: 'Get a word',
})

export const apiDefinition = makeApi([getWord])

export type ApiDefinition = typeof apiDefinition

api.ts

import { QueryClient } from '@tanstack/react-query'
import { Zodios } from '@zodios/core'
import { ZodiosHooks } from '@zodios/react'

import { apiDefinition } from './api-def'

export const apiClient = new Zodios(process.env.EXPO_PUBLIC_API_URL, apiDefinition, {
  validate: true,
  transform: 'response',
})

export const apiHooks = new ZodiosHooks('word-api', apiClient)
export const queryClient = new QueryClient()

app.tsx

import { QueryClientProvider } from '@tanstack/react-query'

import { Component } from './component'
import { queryClient } from '../api'

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Component />
    </QueryClientProvider>
  )
}

component.tsx

import { FunctionComponent } from 'react'
import { View } from 'react-native'
import { Text } from 'react-native-paper'

import { apiHooks } from '../api'

export const Component: FunctionComponent = (props) => {
  const {
    data: wordData,
    isSuccess,
    isLoading,
  } = apiHooks.getWord(
    {},
    {
      // TODO: debug infinite loop
     enabled: false
    },
  )

  console.log('render')

  return (
    <View>
      <Text>
        {!isLoading
          ? isSuccess ? wordData?.word ?? 'none' : 'erreur'
          : 'loading'
        }
      </Text>
    </View>
  )
}

Once I remove enabled: true (or even call refetch()); I get an infinite loop

render
render
render
render
...

(I confirm the HTTP request resolves correctly to 200. In fact the infinite loop ONLY happens if it's 200. Otherwise, it just retries correctly occasionally)


I tried

    {
      // TODO: debug infinite loop
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      refetchInterval: false,
      refetchIntervalInBackground: false,
      refetchOnMount: false,
      retry: false,
      staleTime: 1000 * 60,
    },

Without any difference


Any idea of what I might be doing wrong ? Thanks !
NB: as you noticed, I'm on react-native

Type error when destructuring from hooks

I ran into a somewhat unexpected issue -- when destructuring useInfiniteQuery from hooks a type error is thrown:

Cannot read properties of undefined (reading 'apiName')

Minimal example:

const hooks = new ZodiosHooks('jsonplaceholder', api)
const { useInfiniteQuery } = hooks 

const Component = () => {
  const { data, fetchNextPage } = useInfiniteQuery(...)
}

Repro: https://codesandbox.io/s/zodios-infinite-query-issue-lo5nlb?file=/src/Data.tsx

It took me a moment to realize that I was destructuring a class method, hence why the this context of the class was undefined, causing the error to be thrown.

So obviously rewriting the call as hooks.useInfiniteQuery() works as expected.

I think there are a couple of ways that the code could be modified to ensure that the context exists when used in this manner.

  1. Bind the this context to the method in the constructor:
class Hooks {
    constructor() {
        this.useInfiniteQuery = this.useInfiniteQuery.bind(this);
    }
    useInfiniteQuery = function() {
        console.log(this);
    }
}
  1. Use an arrow function to define the method:
class Hooks {
    useInfiniteQuery = () => {
        console.log(this);
    }
}

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • chore(deps): update dependency rimraf to v5.0.7
  • chore(deps): update testing-library monorepo (@testing-library/dom, @testing-library/jest-dom, @testing-library/react, @testing-library/user-event)
  • chore(deps): update dependency zod to v3.23.8
  • chore(deps): update github/codeql-action action to v3
  • chore(deps): update orhun/git-cliff-action action to v3
  • chore(deps): update testing-library monorepo (major) (@testing-library/dom, @testing-library/react)
  • 🔐 Create all rate-limited PRs at once 🔐

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • github/codeql-action v2
  • github/codeql-action v2
.github/workflows/publish.yml
  • actions/checkout v4
  • actions/setup-node v4
  • orhun/git-cliff-action v2
  • actions/create-release v1
npm
package.json
  • @tanstack/react-query 4.36.1
  • @testing-library/dom 9.3.3
  • @testing-library/jest-dom 6.1.4
  • @testing-library/react 14.0.0
  • @testing-library/react-hooks 8.0.1
  • @testing-library/user-event 14.5.1
  • @types/axios 0.14.0
  • @types/cors 2.8.14
  • @types/express 4.17.19
  • @types/jest 29.5.5
  • @types/node 20.8.9
  • @types/react 18.2.30
  • @zodios/core 10.9.6
  • axios 1.5.1
  • cors 2.8.5
  • express 4.18.2
  • jest 29.7.0
  • jest-environment-jsdom 29.7.0
  • react 18.2.0
  • react-dom 18.2.0
  • react-test-renderer 18.2.0
  • rimraf 5.0.5
  • ts-jest 29.1.1
  • ts-node 10.9.1
  • typescript 5.2.2
  • zod 3.22.4
  • @tanstack/react-query 4.x
  • @zodios/core >=10.2.0 <11.0.0
  • react >=16.8.0
  • @types/react 18.2.30

  • Check this box to trigger a request for Renovate to run again on this repository

Handling errors

Hi,

I would like to ask, if this is correct typing of Zodios error type.

The default type of error in onError callback is: error: Error | ZodiosError | AxiosError<unknown, any> | null.

This error object does not have assigned ErrorResponseSchema type which it should have (it has uknown type). The actual reponse is AxiosError object which doesn not contain ErrorResponseSchema type.

Do I structure my OpenAPI wrong?

// Schema
const ErrorResponseSchema = z.object({
  statusCode: z.number().int(),
  message: z.string().optional(),
  error: z.string()
});

// Generated object
{
  method: 'post',
  path: '/api/...',
  requestFormat: 'json',
  parameters: [],
  response: z.string(),
  errors: [
    {
      status: 400,
      schema: ErrorResponseSchema
    }
  ]
}
/api/...:
  post:
    operationId: someid
    parameters:
      ...
    requestBody:
      ...
    responses:
      '201':
        content:
          application/json:
            schema:
              type: string
      '400':
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ErrorResponseSchema'
    tags: *ref_1

[Feature Request]: Expose React-Query's useInfiniteQuery hook in ZodiosHooks

Describe the feature you'd like to request

Would love a way to access useInfiniteQuery via Zodios Hooks

Example:

const {
  data: posts
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  isLoading,
  isError,
} = zodiosHooks.useInfiniteQuery("/posts");

TypeScript error on excess properties for `configOptions`

Hi, I hope you're great, I was just working with this and spent almost an hour trying to find why refetchInterval was not working for me, and turns out it was an issue similar to the one in #270, I was doing:

const site = useGetSiteById({ params: { id }, options: { refetchInterval: ... } });

and it took some time to get to the error since there's no TypeScript error for excess properties in configOptions, is it possible to make ts error on these cases?

Happy to help with a PR if you could give me some pointers.

Thanks!

Is this project still maintained?

Given that the last release was a year ago, I was wondering if this project is still being maintained.

If not then perhaps this repository can be marked as unmaintained and the community can begin work on an alternative.

"invalidate" property missing when using alias hooks

const { isLoading, data, invalidate } = zodiosHook.useGet("users/:userId", { params: { userId: userId } });
^ works

const { isLoading, data, invalidate } = zodiosHook.useGetUser({ params: { userId: userId } });
^ gives me a red squiggly under "invalidate"

ts error:
Property 'invalidate' does not exist on type 'UseQueryResult<{ merchantId?: string | null | undefined; paypalId?: string | null | undefined; logoUrl?: string | null | undefined; displayTitle?: string | null | undefined; description?: string | null | undefined; ... 24 more ...; passwordResetCount: number; }, unknown>'.ts(2339)

useQuery generated key is not equal to getKeyBy[...] result, when config is undefined

Hi, I'm using zodios-react hooks with a lot of joy, it is really helpful and type-safe. I don't need to waist more time to make hooks :)
But I found an unexpected result about the generated key.

The problem happens when I call a query without any configs like hooks.useGetUsers().
But the query key is not equal to hooks.getKeyByAlias('getUsers'), so I couldn't invalidate that query if I want to call and update in different pages (because I cannot use invalidate() method from the useQuery result).

What I wanted from source code:
hooks.spec.tsx

it ("should match generated useQuery key with getKey result", async () => {
    const { result, waitFor } = renderHook(
      () => apiHooks.useGet("/users"),
      { wrapper }
    );
    await waitFor(() => result.current.isSuccess);
    expect(result.current.key).toEqual(apiHooks.getKeyByPath("get", "/users"));
  })

hooks.ts

getKeyBy[...]<...>(
  ...
) {
    const endpoint = this.getEndpointByPath(method, path);
    if (!endpoint) {
      throw new Error(`No endpoint found for path '${method} ${path}'`);
    }
    const params = pick(config as AnyZodiosMethodOptions | undefined, [
      "params",
      "queries",
    ]);
    return [{ api: this.apiName, path: endpoint.path }, params] as QueryKey;
  }

It's because the pick() function always returns an object even a config is undefined,
therefore queryResult.key is set like this [{api: '...', path: '...'}, {}].

If pick() returns an object every time, the result of the useQuery key also should be always length two.

But the functions getKeyByPath and getKeyByAlias still return length 1 array when the config is undefined.

In this case, useQuery without any params or configs, we cannot invalidate that query because the key, generated, announced to queryClient is not equal to getKeyBy...() result.

(Anyway, if I were you, I'll fix pick() to return undefined when config is empty to make the QueryKey more clean)

[Improvement] Enabling suspense

Allow enabling React Query's suspense setting via ZodiosHooks options argument and have the types inferred correctly. Ensure that it can be opted-out on a per-query basis.

Also, if suspense is enabled on a per-query basis via useQuery, the inferred type doesn't get updated to status and error.

This will also be particularly useful for Next.js' new app router loading/error states.

useInfiniteQuery does not work when using template literal in endpoint path

Hi,

big fan of the library so far.

Unfortunately I ran into an issue regarding infinite queries.
Our backend consists of multiple micro services. I need to be able to swap out the base url for each service individually so I use the whole url as a path value in makeEndpoint.

Example:

export const getSpaces = makeEndpoint({
    method: "get",
    path: `${SPACE_SERVICE_BASE_URL}api/v1/spaces`,
    response: PaginatedSpacesSchema,
    parameters: PaginationParamsSchema,
    alias: "getSpaces",
});

(SPACE_SERVICE_BASE_URL is something like https://foobar.com/)

This works well when calling the getSpaces on the client object or useGetSpaces.

Because of the template literal the type of the path is "${any}api/v1/spaces". So in theory I should be able to call useInfiniteQuery like the following if I am not mistaken:

hooks.useInfiniteQuery("${any}api/v1/spaces", {queries: {...}})

Unfortunately useInfiniteQuery doesn't work if the path type comes from a string with a template literal. ("No endpoint found for ..." and the types for the config parameters etc. don't work).

If this is the expected behavior / a typescript limitation, is there a way to the set a base url for a multiple endpoints?

If you need any additional infos let me know. I am also willing to help out with a PR if needed.

Thank you in advance, keep up the good work.

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.