Skip to content

Commit

Permalink
Merge pull request #97 from 7nohe/feature/add-query-key-function
Browse files Browse the repository at this point in the history
feat: add query key function
  • Loading branch information
seriouslag authored Apr 30, 2024
2 parents abc82b2 + e7b9457 commit 49aab1f
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 61 deletions.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,75 @@ function App() {
export default App;
```

##### Using Mutation hooks

```tsx
// App.tsx
import { usePetServiceAddPet } from "../openapi/queries";

function App() {
const { mutate } = usePetServiceAddPet();

const handleAddPet = () => {
mutate({ name: "Fluffy", status: "available" });
};

return (
<div className="App">
<h1>Add Pet</h1>
<button onClick={handleAddPet}>Add Pet</button>
</div>
);
}

export default App;
```

##### Invalidating queries after mutation

Invalidating queries after a mutation is important to ensure the cache is updated with the new data. This is done by calling the `queryClient.invalidateQueries` function with the query key used by the query hook.

Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation).

To ensure the query key is created the same way as the query hook, you can use the query key function exported by the generated query hooks.

```tsx
import {
usePetServiceFindPetsByStatus,
usePetServiceAddPet,
UsePetServiceFindPetsByStatusKeyFn,
} from "../openapi/queries";

// App.tsx
function App() {
const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
const { mutate } = usePetServiceAddPet({
onSuccess: () => {
queryClient.invalidateQueries({
// Call the query key function to get the query key, this is important to ensure the query key is created the same way as the query hook, this insures the cache is invalidated correctly and is typed correctly
queryKey: [UsePetServiceFindPetsByStatusKeyFn()],
});
},
});

return (
<div className="App">
<h1>Pet List</h1>
<ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
<button
onClick={() => {
mutate({ name: "Fluffy", status: "available" });
}}
>
Add Pet
</button>
</div>
);
}

export default App;
```

##### Runtime Configuration

You can modify the default values used by the generated service calls by modifying the OpenAPI configuration singleton object.
Expand Down
4 changes: 2 additions & 2 deletions examples/react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "./App.css";
import {
useDefaultServiceAddPet,
useDefaultServiceFindPets,
useDefaultServiceFindPetsKey,
UseDefaultServiceFindPetsKeyFn,
useDefaultServiceGetNotDefined,
useDefaultServicePostNotDefined,
} from "../openapi/queries";
Expand Down Expand Up @@ -54,7 +54,7 @@ function App() {
{
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [useDefaultServiceFindPetsKey],
queryKey: UseDefaultServiceFindPetsKeyFn(),
});
console.log("success");
},
Expand Down
8 changes: 8 additions & 0 deletions src/common.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ export const TData = ts.factory.createIdentifier("TData");
export const TError = ts.factory.createIdentifier("TError");
export const TContext = ts.factory.createIdentifier("TContext");

export const EqualsOrGreaterThanToken = ts.factory.createToken(
ts.SyntaxKind.EqualsGreaterThanToken
);

export const QuestionToken = ts.factory.createToken(
ts.SyntaxKind.QuestionToken
);

export const queryKeyGenericType =
ts.factory.createTypeReferenceNode("TQueryKey");
export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
Expand Down
7 changes: 6 additions & 1 deletion src/createExports.mts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export const createExports = (service: Service) => {
];

const commonInQueries = allQueries
.map(({ apiResponse, returnType, key }) => [apiResponse, returnType, key])
.map(({ apiResponse, returnType, key, queryKeyFn }) => [
apiResponse,
returnType,
key,
queryKeyFn,
])
.flat();
const commonInMutations = allMutations
.map(({ mutationResult }) => [mutationResult])
Expand Down
159 changes: 118 additions & 41 deletions src/createUseQuery.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { MethodDeclaration } from "ts-morph";
import {
BuildCommonTypeName,
capitalizeFirstLetter,
EqualsOrGreaterThanToken,
extractPropertiesFromObjectParam,
getNameFromMethod,
getShortType,
queryKeyConstraint,
queryKeyGenericType,
QuestionToken,
TData,
TError,
} from "./common.mjs";
Expand Down Expand Up @@ -61,12 +63,15 @@ export const createApiResponseType = ({
);

return {
/** DefaultResponseDataType
/**
* DefaultResponseDataType
*
* export type MyClassMethodDefaultResponse = Awaited<ReturnType<typeof myClass.myMethod>>
*/
apiResponse,
/**
* will be the name of the type of the response type of the method
* This will be the name of the type of the response type of the method
*
* MyClassMethodDefaultResponse
*/
responseDataType,
Expand Down Expand Up @@ -128,6 +133,7 @@ export function getRequestParamFromMethod(method: MethodDeclaration) {

/**
* Return Type
*
* export const classNameMethodNameQueryResult<TData = MyClassMethodDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
*/
export function createReturnTypeExport({
Expand Down Expand Up @@ -311,7 +317,7 @@ export function createQueryHook({
),
],
undefined,
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
EqualsOrGreaterThanToken,
ts.factory.createCallExpression(
ts.factory.createIdentifier(queryString),
[
Expand All @@ -322,41 +328,28 @@ export function createQueryHook({
ts.factory.createObjectLiteralExpression([
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier("queryKey"),
ts.factory.createArrayLiteralExpression(
[
BuildCommonTypeName(queryKey),
ts.factory.createSpreadElement(
ts.factory.createParenthesizedExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("queryKey"),
ts.factory.createToken(
ts.SyntaxKind.QuestionQuestionToken
),
method.getParameters().length
? ts.factory.createArrayLiteralExpression([
ts.factory.createObjectLiteralExpression(
method
.getParameters()
.map((param) =>
extractPropertiesFromObjectParam(
param
).map((p) =>
ts.factory.createShorthandPropertyAssignment(
ts.factory.createIdentifier(
p.name
)
)
)
)
.flat()
),
])
: ts.factory.createArrayLiteralExpression([])
)
)
),
],
false
ts.factory.createCallExpression(
BuildCommonTypeName(getQueryKeyFnName(queryKey)),
undefined,

method.getParameters().length
? [
ts.factory.createObjectLiteralExpression(
method
.getParameters()
.map((param) =>
extractPropertiesFromObjectParam(param).map(
(p) =>
ts.factory.createShorthandPropertyAssignment(
ts.factory.createIdentifier(p.name)
)
)
)
.flat()
),
ts.factory.createIdentifier("queryKey"),
]
: []
)
),
ts.factory.createPropertyAssignment(
Expand All @@ -366,9 +359,7 @@ export function createQueryHook({
undefined,
[],
undefined,
ts.factory.createToken(
ts.SyntaxKind.EqualsGreaterThanToken
),
EqualsOrGreaterThanToken,
ts.factory.createAsExpression(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
Expand Down Expand Up @@ -463,11 +454,97 @@ export const createUseQuery = ({
queryKey,
});

const queryKeyFn = createQueryKeyFnExport(queryKey, method);

return {
apiResponse: defaultApiResponse,
returnType: returnTypeExport,
key: queryKeyExport,
queryHook: hookWithJsDoc,
suspenseQueryHook: suspenseHookWithJsDoc,
queryKeyFn,
};
};

function getQueryKeyFnName(queryKey: string) {
return `${capitalizeFirstLetter(queryKey)}Fn`;
}

function createQueryKeyFnExport(queryKey: string, method: MethodDeclaration) {
const params = getRequestParamFromMethod(method);

// override key is used to allow the user to override the the queryKey values
const overrideKey = ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier("queryKey"),
QuestionToken,
ts.factory.createTypeReferenceNode("Array<unknown>", [])
);

return ts.factory.createVariableStatement(
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier(getQueryKeyFnName(queryKey)),
undefined,
undefined,
ts.factory.createArrowFunction(
undefined,
undefined,
params ? [params, overrideKey] : [],
undefined,
EqualsOrGreaterThanToken,
queryKeyFn(queryKey, method)
)
),
],
ts.NodeFlags.Const
)
);
}

function queryKeyFn(
queryKey: string,
method: MethodDeclaration
): ts.Expression {
const params = getRequestParamFromMethod(method);

if (!params) {
return ts.factory.createArrayLiteralExpression([
ts.factory.createIdentifier(queryKey),
]);
}

return ts.factory.createArrayLiteralExpression(
[
ts.factory.createIdentifier(queryKey),
ts.factory.createSpreadElement(
ts.factory.createParenthesizedExpression(
ts.factory.createBinaryExpression(
ts.factory.createIdentifier("queryKey"),
ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
method.getParameters().length
? ts.factory.createArrayLiteralExpression([
ts.factory.createObjectLiteralExpression(
method
.getParameters()
.map((param) =>
extractPropertiesFromObjectParam(param).map((p) =>
ts.factory.createShorthandPropertyAssignment(
ts.factory.createIdentifier(p.name)
)
)
)
.flat()
),
])
: ts.factory.createArrayLiteralExpression([])
)
)
),
],
false
);
}
Loading

0 comments on commit 49aab1f

Please sign in to comment.