Skip to content

Commit 85fb384

Browse files
authored
Merge pull request #16 from marklearst/docs/mutation-return-types
docs(mutations): document return type semantics for mutation hooks
2 parents f24002b + 951c858 commit 85fb384

6 files changed

Lines changed: 140 additions & 18 deletions

File tree

src/hooks/useBulkUpdateVariables.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,35 @@ import { mutator } from 'api/mutator'
1515
* This hook is designed to perform a batch operation for creating, updating, and deleting variables, collections, and modes.
1616
* It provides an ergonomic API with `mutate` and loading/error state for easy integration.
1717
*
18+
* ## Return Value
19+
*
20+
* The `mutate` function returns `Promise<TData | undefined>`:
21+
* - On success: Returns the API response data
22+
* - On error: Returns `undefined` (error stored in `error` state)
23+
*
24+
* Use `isSuccess`/`isError` flags or check the return value to handle results.
25+
*
26+
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
27+
* `data` (API response), and `error` (if failed).
28+
*
1829
* @example
1930
* ```tsx
2031
* import { useBulkUpdateVariables } from '@figma-vars/hooks';
2132
*
2233
* function BulkUpdateButton() {
23-
* const { mutate, isLoading, error } = useBulkUpdateVariables();
34+
* const { mutate, isLoading, isError, error } = useBulkUpdateVariables();
2435
*
25-
* const handleBulkUpdate = () => {
26-
* mutate({
36+
* const handleBulkUpdate = async () => {
37+
* const result = await mutate({
2738
* variables: [{ action: 'UPDATE', id: 'VariableId:123', name: 'new-name' }],
2839
* });
40+
* if (result) {
41+
* console.log('Bulk update successful');
42+
* }
2943
* };
3044
*
3145
* if (isLoading) return <div>Updating...</div>;
32-
* if (error) return <div>Error: {error.message}</div>;
46+
* if (isError) return <div>Error: {error?.message}</div>;
3347
* return <button onClick={handleBulkUpdate}>Bulk Update</button>;
3448
* }
3549
* ```

src/hooks/useCreateVariable.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,37 @@ import { mutator } from 'api/mutator'
1414
* @remarks
1515
* The hook returns a `mutate` function to trigger the creation along with state flags and data.
1616
*
17+
* ## Return Value
18+
*
19+
* The `mutate` function returns `Promise<TData | undefined>`:
20+
* - On success: Returns the API response data
21+
* - On error: Returns `undefined` (error stored in `error` state)
22+
*
23+
* Use `isSuccess`/`isError` flags or check the return value to handle results.
24+
*
25+
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
26+
* `data` (API response), and `error` (if failed).
27+
*
1728
* @example
1829
* ```tsx
1930
* import { useCreateVariable } from '@figma-vars/hooks';
2031
*
2132
* function CreateVariableButton() {
22-
* const { mutate, isLoading, error } = useCreateVariable();
33+
* const { mutate, isLoading, isError, error } = useCreateVariable();
2334
*
24-
* const handleCreate = () => {
25-
* mutate({ name: 'new-variable', variableCollectionId: 'VariableCollectionId:1:1', resolvedType: 'COLOR' });
35+
* const handleCreate = async () => {
36+
* const result = await mutate({
37+
* name: 'new-variable',
38+
* variableCollectionId: 'VariableCollectionId:1:1',
39+
* resolvedType: 'COLOR'
40+
* });
41+
* if (result) {
42+
* console.log('Created successfully:', result);
43+
* }
2644
* };
2745
*
2846
* if (isLoading) return <div>Creating...</div>;
29-
* if (error) return <div>Error: {error.message}</div>;
47+
* if (isError) return <div>Error: {error?.message}</div>;
3048
* return <button onClick={handleCreate}>Create Variable</button>;
3149
* }
3250
* ```

src/hooks/useDeleteVariable.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,33 @@ import { mutator } from 'api/mutator'
1313
* @remarks
1414
* This hook provides a `mutate` function to trigger the deletion and exposes loading and error states.
1515
*
16+
* ## Return Value
17+
*
18+
* The `mutate` function returns `Promise<TData | undefined>`:
19+
* - On success: Returns the API response data
20+
* - On error: Returns `undefined` (error stored in `error` state)
21+
*
22+
* Use `isSuccess`/`isError` flags or check the return value to handle results.
23+
*
24+
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
25+
* `data` (API response), and `error` (if failed).
26+
*
1627
* @example
1728
* ```tsx
1829
* import { useDeleteVariable } from '@figma-vars/hooks';
1930
*
2031
* function DeleteVariableButton({ id }: { id: string }) {
21-
* const { mutate, isLoading, error } = useDeleteVariable();
32+
* const { mutate, isLoading, isError, error } = useDeleteVariable();
2233
*
23-
* const onDelete = () => mutate(id);
34+
* const onDelete = async () => {
35+
* const result = await mutate(id);
36+
* if (result) {
37+
* console.log('Deleted successfully');
38+
* }
39+
* };
2440
*
2541
* if (isLoading) return <div>Deleting...</div>;
26-
* if (error) return <div>Error: {error.message}</div>;
42+
* if (isError) return <div>Error: {error?.message}</div>;
2743
* return <button onClick={onDelete}>Delete Variable</button>;
2844
* }
2945
* ```

src/hooks/useUpdateVariable.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,33 @@ import { mutator } from 'api/mutator'
1414
* @remarks
1515
* The hook returns a `mutate` function to trigger the update with given payload and exposes state flags.
1616
*
17+
* ## Return Value
18+
*
19+
* The `mutate` function returns `Promise<TData | undefined>`:
20+
* - On success: Returns the API response data
21+
* - On error: Returns `undefined` (error stored in `error` state)
22+
*
23+
* Use `isSuccess`/`isError` flags or check the return value to handle results.
24+
*
25+
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
26+
* `data` (API response), and `error` (if failed).
27+
*
1728
* @example
1829
* ```tsx
1930
* import { useUpdateVariable } from '@figma-vars/hooks';
2031
*
2132
* function UpdateVariableButton({ id }: { id: string }) {
22-
* const { mutate, isLoading, error } = useUpdateVariable();
33+
* const { mutate, isLoading, isError, error } = useUpdateVariable();
2334
*
24-
* const onUpdate = () => mutate({ variableId: id, payload: { name: 'new-name' } });
35+
* const onUpdate = async () => {
36+
* const result = await mutate({ variableId: id, payload: { name: 'new-name' } });
37+
* if (result) {
38+
* console.log('Updated successfully');
39+
* }
40+
* };
2541
*
2642
* if (isLoading) return <div>Updating...</div>;
27-
* if (error) return <div>Error: {error.message}</div>;
43+
* if (isError) return <div>Error: {error?.message}</div>;
2844
* return <button onClick={onUpdate}>Update Variable</button>;
2945
* }
3046
* ```

src/types/mutations.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,15 @@ export interface MutationState<TData> {
310310
*/
311311
export interface MutationOptions {
312312
/**
313-
* If true, errors will be rethrown instead of being caught and stored in state.
314-
* This allows callers to use try/catch for error handling.
313+
* Controls error handling behavior for the mutation.
314+
*
315+
* - **`false` (default)**: Errors are caught and stored in the `error` state.
316+
* The `mutate` function returns `undefined` on error.
317+
* Use the `isError` flag and `error` state to handle failures reactively.
318+
*
319+
* - **`true`**: Errors are rethrown, allowing try/catch error handling.
320+
* The `mutate` function throws on error.
321+
* Use this when you need imperative error handling.
315322
*
316323
* @default false
317324
*/
@@ -322,19 +329,70 @@ export interface MutationOptions {
322329
* Return value of mutation hooks.
323330
*
324331
* @remarks
325-
* Combines mutation state with a `mutate` trigger function accepting a payload, along with convenient booleans for status.
332+
* Combines mutation state with a `mutate` trigger function accepting a payload,
333+
* along with convenient booleans for status checking.
334+
*
335+
* ## Return Value Semantics
336+
*
337+
* The `mutate` function returns `Promise<TData | undefined>`:
338+
*
339+
* - **On success**: Returns the mutation result data (`TData`)
340+
* - **On error with `throwOnError: false` (default)**: Returns `undefined` and stores error in `error` state
341+
* - **On error with `throwOnError: true`**: Throws the error (use try/catch)
342+
*
343+
* ## Recommended Patterns
344+
*
345+
* ```ts
346+
* // Pattern 1: Check return value (when throwOnError is false)
347+
* const result = await mutate(payload);
348+
* if (result === undefined) {
349+
* // Check error state
350+
* console.error('Mutation failed:', error);
351+
* } else {
352+
* // Use result
353+
* console.log('Created:', result);
354+
* }
355+
*
356+
* // Pattern 2: Use try/catch (when throwOnError is true)
357+
* try {
358+
* const result = await mutate(payload);
359+
* console.log('Created:', result);
360+
* } catch (err) {
361+
* console.error('Mutation failed:', err);
362+
* }
363+
*
364+
* // Pattern 3: Use status flags (reactive)
365+
* if (isSuccess) {
366+
* console.log('Created:', data);
367+
* }
368+
* if (isError) {
369+
* console.error('Failed:', error);
370+
* }
371+
* ```
326372
*
327373
* @typeParam TData - The type of data returned by the mutation.
328374
* @typeParam TPayload - The payload type accepted by the mutation trigger.
329375
*
330376
* @public
331377
*/
332378
export interface MutationResult<TData, TPayload> {
379+
/**
380+
* Trigger the mutation with the given payload.
381+
*
382+
* @returns Promise resolving to the mutation result, or `undefined` if an error occurred
383+
* and `throwOnError` is false. When `throwOnError` is true, errors are thrown instead.
384+
*/
333385
mutate: (payload: TPayload) => Promise<TData | undefined>
386+
/** Current mutation status: 'idle' | 'loading' | 'success' | 'error' */
334387
status: 'idle' | 'loading' | 'success' | 'error'
388+
/** The result data from a successful mutation, or `null` if not yet successful. */
335389
data: TData | null
390+
/** The error from a failed mutation, or `null` if no error. */
336391
error: Error | null
392+
/** `true` while the mutation is in progress. */
337393
isLoading: boolean
394+
/** `true` after a successful mutation. */
338395
isSuccess: boolean
396+
/** `true` after a failed mutation. */
339397
isError: boolean
340398
}

src/utils/retry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function withRetry<T>(
122122
}
123123

124124
// This should never be reached, but TypeScript needs it
125-
/* istanbul ignore next */
125+
/* c8 ignore next */
126126
throw lastError ?? new Error('Retry failed')
127127
}
128128
}

0 commit comments

Comments
 (0)