@ashraflabs/use-api
Version:
A simple and powerful custom React hook (useApi) that simplifies API requests with automatic loading and error states. Ideal for clean and maintainable data fetching in React components.
82 lines (81 loc) • 4.26 kB
JavaScript
import { useState } from "react";
import { FetchError } from "./types/FetchError";
import { removeMultipleSlashes } from "./utils/removeMultipleSlashes";
import { dataToFormData } from "./utils/dataToFormData";
/**
* A custom React hook for making PUT requests using the Fetch API.
* @template PutDataType The type of the data to put.
* @template ResponseType The type of the response data.
* @param baseApiUrl The base URL for the API (e.g., "http://localhost:5000").
* @param url The API endpoint URL (relative).
* @param options Optional configuration for the PUT request.
* @param options.onResponse Callback invoked with the response data on success.
* @param options.onErrorResponse Callback invoked with a FetchError on failure.
* @param options.convertToFormData If true, converts the payload to FormData.
* @param options.removeIfValueIsNullOrUndefined If true, removes null/undefined values from the payload.
* @param options.headers Custom headers for the request.
* @returns An object containing the error, loading state, and a putData function.
* @throws {FetchError} If the request fails or is not aborted.
* @example
* const { error, loading, putData } = usePut<{ id: string, name: string }, { message: string }>(
* "http://localhost:5000",
* "/api/update",
* { convertToFormData: false }
* );
* putData({ id: "123", name: "Updated" });
*/
export function usePut(baseApiUrl, url, options) {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
/**
* Sends a PUT request with the provided data.
* @param dataToPut The data to send in the request body.
* @returns A promise resolving to the response data.
*/
async function putData(dataToPut) {
var _a, _b, _c, _d;
setLoading(true);
const cleanedUrl = removeMultipleSlashes(`${baseApiUrl}/${url}`);
let responseCode;
try {
let payload = dataToPut;
if (options === null || options === void 0 ? void 0 : options.convertToFormData) {
payload = dataToFormData(dataToPut, (_a = options.removeIfValueIsNullOrUndefined) !== null && _a !== void 0 ? _a : false);
}
else if (options === null || options === void 0 ? void 0 : options.removeIfValueIsNullOrUndefined) {
payload = Object.fromEntries(Object.entries(dataToPut).filter(([, value]) => value !== null && value !== undefined));
}
const response = await fetch(cleanedUrl, {
method: "PUT",
headers: {
...((options === null || options === void 0 ? void 0 : options.headers) || {}),
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
},
body: payload instanceof FormData ? payload : JSON.stringify(payload),
});
responseCode = response.status;
if (!response.ok) {
throw new FetchError(`HTTP error! Status: ${response.status}`, response, response.status);
}
const res = await response.json();
setError(null);
(_b = options === null || options === void 0 ? void 0 : options.onResponse) === null || _b === void 0 ? void 0 : _b.call(options, res);
return res;
}
catch (err) {
if (err instanceof Error && err.name !== "AbortError") {
const fetchErr = err instanceof FetchError ? err : new FetchError(err.message);
setError(fetchErr);
(_c = options === null || options === void 0 ? void 0 : options.onErrorResponse) === null || _c === void 0 ? void 0 : _c.call(options, fetchErr);
if (fetchErr.status !== undefined) {
responseCode = fetchErr.status || undefined;
}
}
}
finally {
setLoading(false);
(_d = options === null || options === void 0 ? void 0 : options.onRequestDone) === null || _d === void 0 ? void 0 : _d.call(options, cleanedUrl, removeMultipleSlashes(cleanedUrl.replace(baseApiUrl, "/")).split("?")[0], responseCode);
}
}
return { error, loading, putData };
}