inertiajs-use-api 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { configureUseApi, getUseApiConfig, resetUseApiConfig, } from "./configure.js";
2
+ export { ApiError } from "./errors.js";
3
+ export { useApi } from "./use-api.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AASvC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,59 @@
1
+ import type { ReloadOptions } from "@inertiajs/core";
2
+ export type Method = "get" | "post" | "put" | "patch" | "delete";
3
+ export type FieldErrors<TForm> = Partial<Record<keyof TForm | string, string>>;
4
+ export type QueryParams = Record<string, string | number | boolean | null | undefined>;
5
+ export type IntoProp<TResponse> = string | ((response: TResponse) => Record<string, unknown>);
6
+ export interface SubmitOptions<TResponse, TForm> {
7
+ /** Override the request body. If omitted, the hook's `data` is sent. */
8
+ data?: Partial<TForm>;
9
+ /** Query string params. `null`/`undefined` values are skipped. */
10
+ params?: QueryParams;
11
+ /** Extra headers merged on top of the defaults. */
12
+ headers?: Record<string, string>;
13
+ /** External abort signal. Aborting either this or `cancel()` cancels the request. */
14
+ signal?: AbortSignal;
15
+ /**
16
+ * After success, pipe the response into Inertia page props client-side via
17
+ * `router.replaceProp`. Pass a string to set `page.props[name] = response`,
18
+ * or a function to map the response into a partial props object.
19
+ */
20
+ intoProp?: IntoProp<TResponse>;
21
+ /**
22
+ * After success, trigger an Inertia partial reload for these prop names
23
+ * (`router.reload({ only: [...] })`). Server is source of truth.
24
+ */
25
+ reloadProps?: string | string[];
26
+ /** Extra options forwarded to `router.reload` (merged with `only`). */
27
+ reloadOptions?: Omit<ReloadOptions, "only">;
28
+ /** Value forwarded to the configured `onSuccessToast` handler. */
29
+ successToast?: unknown;
30
+ /**
31
+ * Value forwarded to the configured `onErrorToast` handler. Pass `false` to
32
+ * suppress the default error toast for this call.
33
+ */
34
+ errorToast?: unknown | false;
35
+ onBefore?: () => void;
36
+ onSuccess?: (response: TResponse) => void;
37
+ onError?: (errors: FieldErrors<TForm>, raw: unknown, status: number) => void;
38
+ onFinish?: () => void;
39
+ }
40
+ export interface UseApi<TForm extends object, TResponse = unknown> {
41
+ data: TForm;
42
+ setData: <K extends keyof TForm>(field: K | Partial<TForm>, value?: TForm[K]) => void;
43
+ errors: FieldErrors<TForm>;
44
+ hasErrors: boolean;
45
+ processing: boolean;
46
+ response: TResponse | null;
47
+ wasSuccessful: boolean;
48
+ status: number | null;
49
+ reset: () => void;
50
+ clearErrors: () => void;
51
+ cancel: () => void;
52
+ submit: (method: Method, url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
53
+ get: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
54
+ post: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
55
+ put: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
56
+ patch: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
57
+ delete: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
58
+ }
59
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { UseApi } from "./types.js";
2
+ export declare function useApi<TForm extends object = Record<string, unknown>, TResponse = unknown>(initialData?: TForm): UseApi<TForm, TResponse>;
3
+ //# sourceMappingURL=use-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-api.d.ts","sourceRoot":"","sources":["../src/use-api.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmD,MAAM,EAAE,MAAM,YAAY,CAAC;AAyB1F,wBAAgB,MAAM,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,EACzF,WAAW,GAAE,KAAmB,GAC9B,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CA0K1B"}
@@ -0,0 +1,176 @@
1
+ import { router } from "@inertiajs/core";
2
+ import { useCallback, useRef, useState } from "react";
3
+ import { getUseApiConfig, readXsrfToken } from "./configure.js";
4
+ import { ApiError } from "./errors.js";
5
+ function appendQuery(url, params) {
6
+ if (!params)
7
+ return url;
8
+ const usp = new URLSearchParams();
9
+ for (const [key, value] of Object.entries(params)) {
10
+ if (value === null || value === undefined)
11
+ continue;
12
+ usp.append(key, String(value));
13
+ }
14
+ const qs = usp.toString();
15
+ if (!qs)
16
+ return url;
17
+ return url + (url.includes("?") ? "&" : "?") + qs;
18
+ }
19
+ function resolveUrl(url, baseUrl) {
20
+ if (!baseUrl)
21
+ return url;
22
+ if (/^https?:\/\//i.test(url))
23
+ return url;
24
+ return `${baseUrl.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}`;
25
+ }
26
+ function hasHeader(headers, name) {
27
+ const lower = name.toLowerCase();
28
+ return Object.keys(headers).some((k) => k.toLowerCase() === lower);
29
+ }
30
+ export function useApi(initialData = {}) {
31
+ const initialRef = useRef(initialData);
32
+ const [data, setDataState] = useState(initialData);
33
+ const [errors, setErrors] = useState({});
34
+ const [processing, setProcessing] = useState(false);
35
+ const [response, setResponse] = useState(null);
36
+ const [wasSuccessful, setWasSuccessful] = useState(false);
37
+ const [status, setStatus] = useState(null);
38
+ const inFlightRef = useRef(new Set());
39
+ const setData = useCallback((field, value) => {
40
+ setDataState((prev) => typeof field === "object" ? { ...prev, ...field } : { ...prev, [field]: value });
41
+ }, []);
42
+ const reset = useCallback(() => {
43
+ setDataState(initialRef.current);
44
+ setErrors({});
45
+ setResponse(null);
46
+ setWasSuccessful(false);
47
+ setStatus(null);
48
+ }, []);
49
+ const clearErrors = useCallback(() => setErrors({}), []);
50
+ const cancel = useCallback(() => {
51
+ for (const controller of inFlightRef.current) {
52
+ controller.abort();
53
+ }
54
+ inFlightRef.current.clear();
55
+ }, []);
56
+ const submit = useCallback(async (method, url, options = {}) => {
57
+ const config = getUseApiConfig();
58
+ const controller = new AbortController();
59
+ inFlightRef.current.add(controller);
60
+ if (options.signal) {
61
+ if (options.signal.aborted) {
62
+ controller.abort();
63
+ }
64
+ else {
65
+ options.signal.addEventListener("abort", () => controller.abort(), {
66
+ once: true,
67
+ });
68
+ }
69
+ }
70
+ options.onBefore?.();
71
+ setProcessing(true);
72
+ setErrors({});
73
+ setWasSuccessful(false);
74
+ setStatus(null);
75
+ const fullUrl = appendQuery(resolveUrl(url, config.baseUrl), options.params);
76
+ const xsrf = readXsrfToken();
77
+ const xsrfHeaderName = config.xsrfHeaderName ?? "X-XSRF-TOKEN";
78
+ const headers = {
79
+ Accept: "application/json",
80
+ "X-Requested-With": "XMLHttpRequest",
81
+ ...(config.defaultHeaders ?? {}),
82
+ ...(xsrf ? { [xsrfHeaderName]: xsrf } : {}),
83
+ ...options.headers,
84
+ };
85
+ const body = options.data === undefined ? data : { ...data, ...options.data };
86
+ const hasBody = method !== "get" && body !== undefined && Object.keys(body).length > 0;
87
+ if (hasBody && !hasHeader(headers, "content-type")) {
88
+ headers["Content-Type"] = "application/json";
89
+ }
90
+ let res;
91
+ try {
92
+ try {
93
+ res = await fetch(fullUrl, {
94
+ method: method.toUpperCase(),
95
+ credentials: "include",
96
+ headers,
97
+ body: hasBody ? JSON.stringify(body) : undefined,
98
+ signal: controller.signal,
99
+ });
100
+ }
101
+ catch (networkErr) {
102
+ if (networkErr.name === "AbortError")
103
+ throw networkErr;
104
+ if (options.errorToast !== false && config.onErrorToast) {
105
+ config.onErrorToast(options.errorToast ?? "Network error. Please try again.");
106
+ }
107
+ throw networkErr;
108
+ }
109
+ const contentType = res.headers.get("content-type") ?? "";
110
+ const json = contentType.includes("application/json") ? await res.json() : null;
111
+ setStatus(res.status);
112
+ config.onResponse?.(json, res.status, res.ok);
113
+ if (!res.ok) {
114
+ const flat = (config.parseErrors?.(json, res.status) ?? {});
115
+ setErrors(flat);
116
+ const message = config.parseMessage?.(json, res.status) ?? `Request failed (${res.status})`;
117
+ if (options.errorToast !== false && config.onErrorToast) {
118
+ config.onErrorToast(options.errorToast ?? message);
119
+ }
120
+ options.onError?.(flat, json, res.status);
121
+ throw new ApiError(res.status, message, json);
122
+ }
123
+ const typedResponse = json;
124
+ setResponse(typedResponse);
125
+ setWasSuccessful(true);
126
+ if (options.intoProp) {
127
+ if (typeof options.intoProp === "string") {
128
+ router.replaceProp(options.intoProp, () => typedResponse);
129
+ }
130
+ else {
131
+ const partial = options.intoProp(typedResponse);
132
+ for (const [name, value] of Object.entries(partial)) {
133
+ router.replaceProp(name, () => value);
134
+ }
135
+ }
136
+ }
137
+ if (options.reloadProps) {
138
+ const only = Array.isArray(options.reloadProps) ? options.reloadProps : [options.reloadProps];
139
+ router.reload({ ...(options.reloadOptions ?? {}), only });
140
+ }
141
+ if (options.successToast !== undefined && config.onSuccessToast) {
142
+ config.onSuccessToast(options.successToast);
143
+ }
144
+ options.onSuccess?.(typedResponse);
145
+ return typedResponse;
146
+ }
147
+ finally {
148
+ inFlightRef.current.delete(controller);
149
+ if (inFlightRef.current.size === 0) {
150
+ setProcessing(false);
151
+ }
152
+ options.onFinish?.();
153
+ }
154
+ }, [data]);
155
+ const verb = (method) => (url, options) => submit(method, url, options);
156
+ return {
157
+ data,
158
+ setData,
159
+ errors,
160
+ hasErrors: Object.keys(errors).length > 0,
161
+ processing,
162
+ response,
163
+ wasSuccessful,
164
+ status,
165
+ reset,
166
+ clearErrors,
167
+ cancel,
168
+ submit,
169
+ get: verb("get"),
170
+ post: verb("post"),
171
+ put: verb("put"),
172
+ patch: verb("patch"),
173
+ delete: verb("delete"),
174
+ };
175
+ }
176
+ //# sourceMappingURL=use-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-api.js","sourceRoot":"","sources":["../src/use-api.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "inertiajs-use-api",
3
+ "version": "0.0.0",
4
+ "description": "A React hook for calling JSON API endpoints from Inertia.js apps, with optional piping of responses into Inertia page props.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src",
18
+ "README.md",
19
+ "SKILL.md",
20
+ "AGENTS.md"
21
+ ],
22
+ "sideEffects": false,
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "npm run clean && npm run build",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest",
29
+ "test:run": "vitest run",
30
+ "format": "biome format --write",
31
+ "format:check": "biome format",
32
+ "lint": "biome lint --write",
33
+ "lint:check": "biome lint",
34
+ "apply": "biome check --write"
35
+ },
36
+ "keywords": [
37
+ "inertia",
38
+ "inertiajs",
39
+ "react",
40
+ "hook",
41
+ "api",
42
+ "fetch",
43
+ "laravel"
44
+ ],
45
+ "peerDependencies": {
46
+ "@inertiajs/core": "^2.0.0 || ^3.0.0",
47
+ "react": "^18.0.0 || ^19.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@biomejs/biome": "^2.4.16",
51
+ "@inertiajs/core": "^3.3.1",
52
+ "@testing-library/dom": "^10.4.1",
53
+ "@testing-library/react": "^16.3.2",
54
+ "@types/react": "^19.2.17",
55
+ "@types/react-dom": "^19.2.3",
56
+ "jsdom": "^29.1.1",
57
+ "react": "^19.2.7",
58
+ "react-dom": "^19.2.7",
59
+ "typescript": "^5.9.3",
60
+ "vitest": "^4.1.8"
61
+ }
62
+ }
@@ -0,0 +1,57 @@
1
+ export interface UseApiConfig {
2
+ /** Prepended to relative URLs (e.g. "/api"). Absolute URLs pass through. */
3
+ baseUrl?: string;
4
+ /** Merged onto every request before per-call headers. */
5
+ defaultHeaders?: Record<string, string>;
6
+ /**
7
+ * Reads the CSRF token. Defaults to decoding the `XSRF-TOKEN` cookie.
8
+ * Return `null` for no token.
9
+ */
10
+ getXsrfToken?: () => string | null;
11
+ /** Header name for the CSRF token. Defaults to `X-XSRF-TOKEN`. */
12
+ xsrfHeaderName?: string;
13
+ /**
14
+ * Parses the response body into a flat `{ field: message }` map on a non-2xx response.
15
+ * If unset, no field errors are populated.
16
+ */
17
+ parseErrors?: (body: unknown, status: number) => Record<string, string>;
18
+ /**
19
+ * Extracts a human-readable error message from the body (used as the default error toast).
20
+ */
21
+ parseMessage?: (body: unknown, status: number) => string | null;
22
+ /** Invoked with the `successToast` value the caller passed. */
23
+ onSuccessToast?: (toast: unknown) => void;
24
+ /** Invoked with the resolved error toast value (caller's or the fallback message). */
25
+ onErrorToast?: (toast: unknown) => void;
26
+ /**
27
+ * Inspect every response body (after JSON parse) regardless of status.
28
+ * Useful for pulling out server-side toast envelopes.
29
+ */
30
+ onResponse?: (body: unknown, status: number, ok: boolean) => void;
31
+ }
32
+
33
+ const config: UseApiConfig = {};
34
+
35
+ export function configureUseApi(next: UseApiConfig): void {
36
+ Object.assign(config, next);
37
+ }
38
+
39
+ export function getUseApiConfig(): UseApiConfig {
40
+ return config;
41
+ }
42
+
43
+ export function resetUseApiConfig(): void {
44
+ for (const key of Object.keys(config) as (keyof UseApiConfig)[]) {
45
+ delete config[key];
46
+ }
47
+ }
48
+
49
+ function defaultXsrfReader(): string | null {
50
+ if (typeof document === "undefined") return null;
51
+ const match = document.cookie.match(/(?:^|; )XSRF-TOKEN=([^;]*)/);
52
+ return match && match[1] !== undefined ? decodeURIComponent(match[1]) : null;
53
+ }
54
+
55
+ export function readXsrfToken(): string | null {
56
+ return (config.getXsrfToken ?? defaultXsrfReader)();
57
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,10 @@
1
+ export class ApiError extends Error {
2
+ constructor(
3
+ public readonly status: number,
4
+ message: string,
5
+ public readonly body: unknown,
6
+ ) {
7
+ super(message);
8
+ this.name = "ApiError";
9
+ }
10
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type { UseApiConfig } from "./configure.js";
2
+ export {
3
+ configureUseApi,
4
+ getUseApiConfig,
5
+ resetUseApiConfig,
6
+ } from "./configure.js";
7
+ export { ApiError } from "./errors.js";
8
+ export type {
9
+ FieldErrors,
10
+ IntoProp,
11
+ Method,
12
+ QueryParams,
13
+ SubmitOptions,
14
+ UseApi,
15
+ } from "./types.js";
16
+ export { useApi } from "./use-api.js";
package/src/types.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { ReloadOptions } from "@inertiajs/core";
2
+
3
+ export type Method = "get" | "post" | "put" | "patch" | "delete";
4
+
5
+ export type FieldErrors<TForm> = Partial<Record<keyof TForm | string, string>>;
6
+
7
+ export type QueryParams = Record<string, string | number | boolean | null | undefined>;
8
+
9
+ export type IntoProp<TResponse> = string | ((response: TResponse) => Record<string, unknown>);
10
+
11
+ export interface SubmitOptions<TResponse, TForm> {
12
+ /** Override the request body. If omitted, the hook's `data` is sent. */
13
+ data?: Partial<TForm>;
14
+ /** Query string params. `null`/`undefined` values are skipped. */
15
+ params?: QueryParams;
16
+ /** Extra headers merged on top of the defaults. */
17
+ headers?: Record<string, string>;
18
+ /** External abort signal. Aborting either this or `cancel()` cancels the request. */
19
+ signal?: AbortSignal;
20
+
21
+ /**
22
+ * After success, pipe the response into Inertia page props client-side via
23
+ * `router.replaceProp`. Pass a string to set `page.props[name] = response`,
24
+ * or a function to map the response into a partial props object.
25
+ */
26
+ intoProp?: IntoProp<TResponse>;
27
+ /**
28
+ * After success, trigger an Inertia partial reload for these prop names
29
+ * (`router.reload({ only: [...] })`). Server is source of truth.
30
+ */
31
+ reloadProps?: string | string[];
32
+ /** Extra options forwarded to `router.reload` (merged with `only`). */
33
+ reloadOptions?: Omit<ReloadOptions, "only">;
34
+
35
+ /** Value forwarded to the configured `onSuccessToast` handler. */
36
+ successToast?: unknown;
37
+ /**
38
+ * Value forwarded to the configured `onErrorToast` handler. Pass `false` to
39
+ * suppress the default error toast for this call.
40
+ */
41
+ errorToast?: unknown | false;
42
+
43
+ onBefore?: () => void;
44
+ onSuccess?: (response: TResponse) => void;
45
+ onError?: (errors: FieldErrors<TForm>, raw: unknown, status: number) => void;
46
+ onFinish?: () => void;
47
+ }
48
+
49
+ export interface UseApi<TForm extends object, TResponse = unknown> {
50
+ data: TForm;
51
+ setData: <K extends keyof TForm>(field: K | Partial<TForm>, value?: TForm[K]) => void;
52
+ errors: FieldErrors<TForm>;
53
+ hasErrors: boolean;
54
+ processing: boolean;
55
+ response: TResponse | null;
56
+ wasSuccessful: boolean;
57
+ status: number | null;
58
+ reset: () => void;
59
+ clearErrors: () => void;
60
+ cancel: () => void;
61
+ submit: (method: Method, url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
62
+ get: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
63
+ post: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
64
+ put: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
65
+ patch: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
66
+ delete: (url: string, options?: SubmitOptions<TResponse, TForm>) => Promise<TResponse>;
67
+ }