redux-toolkit icon indicating copy to clipboard operation
redux-toolkit copied to clipboard

feature request: TypedUseQuery

Open ap-justin opened this issue 2 years ago • 2 comments

typing builder.query is easy like

builder.query<ReturnType, ArgType>

and have used it to query blockchain LCD endpoints

queryA - builder.query<A, Z>
queryB - builder.query<B, Z>
queryC - builder.query<C, Z>
queryD - builder.query<D, Z>

type Z = {
  msg: object
  contract: string
}

because queries A,B,C,D have the same query implementation,

query({contract, msg}) => `lcdEndpoint/${contract}/${toBase64(msg)}`
transformResponse: (res: { data: T }) => {
  return res.data
}

I tried to wrap the generic useQuery hook with something like useGenericQuery see at code sandbox

import { BaseQueryFn } from "@reduxjs/toolkit/dist/query";
import { QueryDefinition } from "@reduxjs/toolkit/dist/query/endpointDefinitions";
import { UseQuery } from "@reduxjs/toolkit/dist/query/react/buildHooks";

type Queries = {
  queryA: { args: { id: number }; result: ResultA };
  queryB: { args: { addr: string }; result: ResultB };
  queryC: { args: null; result: ResultC };
};

type Q = keyof Queries;
type Base = BaseQueryFn<any, unknown, unknown, {}, {}>;
type GenericUseQuery<T extends Q> = UseQuery<
  QueryDefinition<Queries[T]["args"], Base, string, Queries[T]["result"], "">
>;

type GenericResult<T extends Q> = TypedUseQueryHookResult<
  Queries[T]["result"],
  Queries[T]["args"],
  Base
>;

export function useGenericQuery<
  T extends Q,
  K extends Parameters<GenericUseQuery<T>>
>(type: T, contract: string, args: K[0], options?: K[1]) {
  return useQuery(
    {
      type,
      contract,
      msg: { balance: {} },
    },
    options
  ) as GenericResult<T>;
}

so that instead of using different endpoints

//ComponentA.tsx
const resultA /** ResultA */ = useQueryA(argsA);

//ComponentB.tsx
const resultB /** ResultB */ = useQueryB(argsB);

//ComponentC.tsx
const resultC /** ResultC */ = useQueryC(argsC);

I could just use one

//ComponentA.tsx
const genericResult /** ResultA */ = useGenericQuery(type:"queryA", {/** ArgsA*/});

//ComponentB.tsx
const genericResult /** ResultB */ = useGenericQuery(type:"queryB", {/** ArgsB*/});

//ComponentC.tsx
const genericResult /** ResultC */ = useGenericQuery(type:"queryC", {/** ArgsC*/});

I had initial success with this approach, but I encountered some difference from just using useQuery as it is

see at code sandbox

  • data is not typed according to return of selectFromResult,
type ResultA = {
  { results: string[] }
}

const { data /** must be string[], but ResultA instead*/ } = useGenericQuery("queryA", "contract_address", {}, {
  selectFromResult: ({ data /** ResultA  */}) => ({
      data: data.results, // string[]
    }),
})
  • data doesn't follow name of selectFromResult
type ResultA = {
  { results: string[] }
}

const { renamedResult /** error, should be 'data' not 'renamedResult' */ } = useGenericQuery("queryA", "contract_address", {}, {
  selectFromResult: ({ data /** ResultA  */}) => ({
      renamedResult: data.results,
    }),
})

some help would be really appreciated to iron out these bumps something like TypedUseQuery would be a life saver!

ap-justin avatar Aug 15 '22 02:08 ap-justin

@ap-justin we actually literally just shipped a TypedUseQueryHookResult type in 1.8.4 :)

https://github.com/reduxjs/redux-toolkit/releases/tag/v1.8.4

markerikson avatar Aug 15 '22 03:08 markerikson

@markerikson, I had success on the result side of things with

type GenericResult<T extends Q> = TypedUseQueryHookResult<
  Queries[T]["result"],
  Queries[T]["args"],
  Base
>;

my problem now is dealing with the options, particularly selectFromResult

type ResultA = {
  { results: string[] }
}

const { data /** must be string[], but ResultA instead*/ } = useGenericQuery("queryA", "contract_address", {}, {
  selectFromResult: ({ data /** ResultA  */}) => ({
      data: data.results, // string[]
    }),
})

kindly see at code sandbox

ap-justin avatar Aug 15 '22 03:08 ap-justin