Skip to content

Commit

Permalink
Merge branch 'ohcnetwork:develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
AnveshNalimela authored Dec 17, 2024
2 parents b0c1f4d + f56e6f5 commit 8df6eb8
Show file tree
Hide file tree
Showing 31 changed files with 665 additions and 465 deletions.
2 changes: 1 addition & 1 deletion .cursorrules
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ UI and Styling

General Guidelines

- Care uses a custom useQuery hook to fetch data from the API. (Docs @ /Utils/request/useQuery)
- Care uses TanStack Query for data fetching from the API along with query and mutate utilities for the queryFn and mutationFn. (Docs @ /Utils/request/README.md)
- APIs are defined in the api.tsx file.
60 changes: 30 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.4",
"@sentry/browser": "^8.42.0",
"@sentry/browser": "^8.45.1",
"@tanstack/react-query": "^5.62.3",
"@tanstack/react-query-devtools": "^5.62.7",
"@yudiel/react-qr-scanner": "^2.0.8",
Expand Down
8 changes: 6 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
MutationCache,
QueryCache,
QueryClient,
QueryClientProvider,
Expand All @@ -16,7 +17,7 @@ import AuthUserProvider from "@/Providers/AuthUserProvider";
import HistoryAPIProvider from "@/Providers/HistoryAPIProvider";
import Routers from "@/Routers";
import { FeatureFlagsProvider } from "@/Utils/featureFlags";
import { handleQueryError } from "@/Utils/request/errorHandler";
import { handleHttpError } from "@/Utils/request/errorHandler";

import { PubSubProvider } from "./Utils/pubsubContext";

Expand All @@ -29,7 +30,10 @@ const queryClient = new QueryClient({
},
},
queryCache: new QueryCache({
onError: handleQueryError,
onError: handleHttpError,
}),
mutationCache: new MutationCache({
onError: handleHttpError,
}),
});

Expand Down
82 changes: 80 additions & 2 deletions src/Utils/request/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ function FacilityDetails({ id }: { id: string }) {
- Integrates with our global error handling.

```typescript
interface QueryOptions {
interface APICallOptions {
pathParams?: Record<string, string>; // URL parameters
queryParams?: Record<string, string>; // Query string parameters
queryParams?: QueryParams; // Query string parameters
body?: TBody; // Request body
silent?: boolean; // Suppress error notifications
headers?: HeadersInit; // Additional headers
}
// Basic usage
Expand Down Expand Up @@ -100,6 +102,82 @@ are automatically handled.

Use the `silent: true` option to suppress error notifications for specific queries.

## Using Mutations with TanStack Query

For data mutations, we provide a `mutate` utility that works seamlessly with TanStack Query's `useMutation` hook.

```tsx
import { useMutation } from "@tanstack/react-query";
import mutate from "@/Utils/request/mutate";
function CreatePrescription({ consultationId }: { consultationId: string }) {
const { mutate: createPrescription, isPending } = useMutation({
mutationFn: mutate(MedicineRoutes.createPrescription, {
pathParams: { consultationId },
}),
onSuccess: () => {
toast.success("Prescription created successfully");
},
});
return (
<Button
onClick={() => createPrescription({ medicineId: "123", dosage: "1x daily" })}
disabled={isPending}
>
Create Prescription
</Button>
);
}
// With path parameters and complex payload
function UpdatePatient({ patientId }: { patientId: string }) {
const { mutate: updatePatient } = useMutation({
mutationFn: mutate(PatientRoutes.update, {
pathParams: { id: patientId },
silent: true // Optional: suppress error notifications
})
});
const handleSubmit = (data: PatientData) => {
updatePatient(data);
};
return <PatientForm onSubmit={handleSubmit} />;
}
```

### mutate

`mutate` is our wrapper around the API call functionality that works with TanStack Query's `useMutation`. It:
- Handles request body serialization
- Sets appropriate headers
- Integrates with our global error handling
- Provides TypeScript type safety for your mutation payload

```typescript
interface APICallOptions {
pathParams?: Record<string, string>; // URL parameters
queryParams?: QueryParams; // Query string parameters
body?: TBody; // Request body
silent?: boolean; // Suppress error notifications
headers?: HeadersInit; // Additional headers
}
// Basic usage
useMutation({
mutationFn: mutate(routes.users.create)
});
// With parameters
useMutation({
mutationFn: mutate(routes.users.update, {
pathParams: { id },
silent: true // Optional: suppress error notifications
})
});
```

## Migration Guide & Reference

### Understanding the Transition
Expand Down
10 changes: 5 additions & 5 deletions src/Utils/request/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { navigate } from "raviger";

import * as Notifications from "@/Utils/Notifications";
import { QueryError } from "@/Utils/request/queryError";
import { HTTPError } from "@/Utils/request/types";

export function handleQueryError(error: Error) {
export function handleHttpError(error: Error) {
if (error.name === "AbortError") {
return;
}

if (!(error instanceof QueryError)) {
if (!(error instanceof HTTPError)) {
Notifications.Error({ msg: error.message || "Something went wrong!" });
return;
}
Expand All @@ -34,7 +34,7 @@ export function handleQueryError(error: Error) {
});
}

function isSessionExpired(error: QueryError["cause"]) {
function isSessionExpired(error: HTTPError["cause"]) {
return (
// If Authorization header is not valid
error?.code === "token_not_valid" ||
Expand All @@ -49,6 +49,6 @@ function handleSessionExpired() {
}
}

function isBadRequest(error: QueryError) {
function isBadRequest(error: HTTPError) {
return error.status === 400 || error.status === 406;
}
26 changes: 26 additions & 0 deletions src/Utils/request/mutate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { callApi } from "@/Utils/request/query";
import { APICallOptions, Route } from "@/Utils/request/types";

/**
* Creates a TanStack Query compatible mutation function.
*
* Example:
* ```tsx
* const { mutate: createPrescription, isPending } = useMutation({
* mutationFn: mutate(MedicineRoutes.createPrescription, {
* pathParams: { consultationId },
* }),
* onSuccess: () => {
* toast.success(t("medication_request_prescribed"));
* },
* });
* ```
*/
export default function mutate<TData, TBody>(
route: Route<TData, TBody>,
options?: APICallOptions<TBody>,
) {
return (variables: TBody) => {
return callApi(route, { ...options, body: variables });
};
}
31 changes: 22 additions & 9 deletions src/Utils/request/query.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import careConfig from "@careConfig";

import { QueryError } from "@/Utils/request/queryError";
import { getResponseBody } from "@/Utils/request/request";
import { QueryOptions, Route } from "@/Utils/request/types";
import { APICallOptions, HTTPError, Route } from "@/Utils/request/types";
import { makeHeaders, makeUrl } from "@/Utils/request/utils";

async function queryRequest<TData, TBody>(
export async function callApi<TData, TBody>(
{ path, method, noAuth }: Route<TData, TBody>,
options?: QueryOptions<TBody>,
options?: APICallOptions<TBody>,
): Promise<TData> {
const url = `${careConfig.apiUrl}${makeUrl(path, options?.queryParams, options?.pathParams)}`;

const fetchOptions: RequestInit = {
method,
headers: makeHeaders(noAuth ?? false),
headers: makeHeaders(noAuth ?? false, options?.headers),
signal: options?.signal,
};

Expand All @@ -32,7 +31,7 @@ async function queryRequest<TData, TBody>(
const data = await getResponseBody<TData>(res);

if (!res.ok) {
throw new QueryError({
throw new HTTPError({
message: "Request Failed",
status: res.status,
silent: options?.silent ?? false,
Expand All @@ -44,13 +43,27 @@ async function queryRequest<TData, TBody>(
}

/**
* Creates a TanStack Query compatible request function
* Creates a TanStack Query compatible query function.
*
* Example:
* ```tsx
* const { data, isLoading } = useQuery({
* queryKey: ["prescription", consultationId],
* queryFn: query(MedicineRoutes.prescription, {
* pathParams: { consultationId },
* queryParams: {
* limit: 10,
* offset: 0,
* },
* }),
* });
* ```
*/
export default function query<TData, TBody>(
route: Route<TData, TBody>,
options?: QueryOptions<TBody>,
options?: APICallOptions<TBody>,
) {
return ({ signal }: { signal: AbortSignal }) => {
return queryRequest(route, { ...options, signal });
return callApi(route, { ...options, signal });
};
}
Loading

0 comments on commit 8df6eb8

Please sign in to comment.