Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/hooks/useBulkUpdateVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,35 @@ import { mutator } from 'api/mutator'
* This hook is designed to perform a batch operation for creating, updating, and deleting variables, collections, and modes.
* It provides an ergonomic API with `mutate` and loading/error state for easy integration.
*
* ## Return Value
*
* The `mutate` function returns `Promise<TData | undefined>`:
* - On success: Returns the API response data
* - On error: Returns `undefined` (error stored in `error` state)
*
* Use `isSuccess`/`isError` flags or check the return value to handle results.
*
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
* `data` (API response), and `error` (if failed).
*
* @example
* ```tsx
* import { useBulkUpdateVariables } from '@figma-vars/hooks';
*
* function BulkUpdateButton() {
* const { mutate, isLoading, error } = useBulkUpdateVariables();
* const { mutate, isLoading, isError, error } = useBulkUpdateVariables();
*
* const handleBulkUpdate = () => {
* mutate({
* const handleBulkUpdate = async () => {
* const result = await mutate({
* variables: [{ action: 'UPDATE', id: 'VariableId:123', name: 'new-name' }],
* });
* if (result) {
* console.log('Bulk update successful');
* }
* };
*
* if (isLoading) return <div>Updating...</div>;
* if (error) return <div>Error: {error.message}</div>;
* if (isError) return <div>Error: {error?.message}</div>;
* return <button onClick={handleBulkUpdate}>Bulk Update</button>;
* }
* ```
Expand Down
26 changes: 22 additions & 4 deletions src/hooks/useCreateVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,37 @@ import { mutator } from 'api/mutator'
* @remarks
* The hook returns a `mutate` function to trigger the creation along with state flags and data.
*
* ## Return Value
*
* The `mutate` function returns `Promise<TData | undefined>`:
* - On success: Returns the API response data
* - On error: Returns `undefined` (error stored in `error` state)
*
* Use `isSuccess`/`isError` flags or check the return value to handle results.
*
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
* `data` (API response), and `error` (if failed).
*
* @example
* ```tsx
* import { useCreateVariable } from '@figma-vars/hooks';
*
* function CreateVariableButton() {
* const { mutate, isLoading, error } = useCreateVariable();
* const { mutate, isLoading, isError, error } = useCreateVariable();
*
* const handleCreate = () => {
* mutate({ name: 'new-variable', variableCollectionId: 'VariableCollectionId:1:1', resolvedType: 'COLOR' });
* const handleCreate = async () => {
* const result = await mutate({
* name: 'new-variable',
* variableCollectionId: 'VariableCollectionId:1:1',
* resolvedType: 'COLOR'
* });
* if (result) {
* console.log('Created successfully:', result);
* }
* };
*
* if (isLoading) return <div>Creating...</div>;
* if (error) return <div>Error: {error.message}</div>;
* if (isError) return <div>Error: {error?.message}</div>;
* return <button onClick={handleCreate}>Create Variable</button>;
* }
* ```
Expand Down
22 changes: 19 additions & 3 deletions src/hooks/useDeleteVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,33 @@ import { mutator } from 'api/mutator'
* @remarks
* This hook provides a `mutate` function to trigger the deletion and exposes loading and error states.
*
* ## Return Value
*
* The `mutate` function returns `Promise<TData | undefined>`:
* - On success: Returns the API response data
* - On error: Returns `undefined` (error stored in `error` state)
*
* Use `isSuccess`/`isError` flags or check the return value to handle results.
*
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
* `data` (API response), and `error` (if failed).
*
* @example
* ```tsx
* import { useDeleteVariable } from '@figma-vars/hooks';
*
* function DeleteVariableButton({ id }: { id: string }) {
* const { mutate, isLoading, error } = useDeleteVariable();
* const { mutate, isLoading, isError, error } = useDeleteVariable();
*
* const onDelete = () => mutate(id);
* const onDelete = async () => {
* const result = await mutate(id);
* if (result) {
* console.log('Deleted successfully');
* }
* };
*
* if (isLoading) return <div>Deleting...</div>;
* if (error) return <div>Error: {error.message}</div>;
* if (isError) return <div>Error: {error?.message}</div>;
* return <button onClick={onDelete}>Delete Variable</button>;
* }
* ```
Expand Down
22 changes: 19 additions & 3 deletions src/hooks/useUpdateVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,33 @@ import { mutator } from 'api/mutator'
* @remarks
* The hook returns a `mutate` function to trigger the update with given payload and exposes state flags.
*
* ## Return Value
*
* The `mutate` function returns `Promise<TData | undefined>`:
* - On success: Returns the API response data
* - On error: Returns `undefined` (error stored in `error` state)
*
* Use `isSuccess`/`isError` flags or check the return value to handle results.
*
* @returns MutationResult with `mutate`, status flags (`isLoading`, `isSuccess`, `isError`),
* `data` (API response), and `error` (if failed).
*
* @example
* ```tsx
* import { useUpdateVariable } from '@figma-vars/hooks';
*
* function UpdateVariableButton({ id }: { id: string }) {
* const { mutate, isLoading, error } = useUpdateVariable();
* const { mutate, isLoading, isError, error } = useUpdateVariable();
*
* const onUpdate = () => mutate({ variableId: id, payload: { name: 'new-name' } });
* const onUpdate = async () => {
* const result = await mutate({ variableId: id, payload: { name: 'new-name' } });
* if (result) {
* console.log('Updated successfully');
* }
* };
*
* if (isLoading) return <div>Updating...</div>;
* if (error) return <div>Error: {error.message}</div>;
* if (isError) return <div>Error: {error?.message}</div>;
* return <button onClick={onUpdate}>Update Variable</button>;
* }
* ```
Expand Down
64 changes: 61 additions & 3 deletions src/types/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,15 @@ export interface MutationState<TData> {
*/
export interface MutationOptions {
/**
* If true, errors will be rethrown instead of being caught and stored in state.
* This allows callers to use try/catch for error handling.
* Controls error handling behavior for the mutation.
*
* - **`false` (default)**: Errors are caught and stored in the `error` state.
* The `mutate` function returns `undefined` on error.
* Use the `isError` flag and `error` state to handle failures reactively.
*
* - **`true`**: Errors are rethrown, allowing try/catch error handling.
* The `mutate` function throws on error.
* Use this when you need imperative error handling.
*
* @default false
*/
Expand All @@ -322,19 +329,70 @@ export interface MutationOptions {
* Return value of mutation hooks.
*
* @remarks
* Combines mutation state with a `mutate` trigger function accepting a payload, along with convenient booleans for status.
* Combines mutation state with a `mutate` trigger function accepting a payload,
* along with convenient booleans for status checking.
*
* ## Return Value Semantics
*
* The `mutate` function returns `Promise<TData | undefined>`:
*
* - **On success**: Returns the mutation result data (`TData`)
* - **On error with `throwOnError: false` (default)**: Returns `undefined` and stores error in `error` state
* - **On error with `throwOnError: true`**: Throws the error (use try/catch)
*
* ## Recommended Patterns
*
* ```ts
* // Pattern 1: Check return value (when throwOnError is false)
* const result = await mutate(payload);
* if (result === undefined) {
* // Check error state
* console.error('Mutation failed:', error);
* } else {
* // Use result
* console.log('Created:', result);
* }
Comment on lines +345 to +354
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Pattern 1, the variable error is referenced without context. The code example shows console.error('Mutation failed:', error); but error is not in scope within this standalone example - it's a property of the MutationResult object. This should be shown as accessing the error from the mutation result object (e.g., destructured from the hook or accessed via dot notation) to provide accurate guidance to developers.

Copilot uses AI. Check for mistakes.
*
* // Pattern 2: Use try/catch (when throwOnError is true)
* try {
* const result = await mutate(payload);
* console.log('Created:', result);
* } catch (err) {
* console.error('Mutation failed:', err);
* }
*
* // Pattern 3: Use status flags (reactive)
* if (isSuccess) {
* console.log('Created:', data);
* }
* if (isError) {
* console.error('Failed:', error);
Comment on lines +364 to +369
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Pattern 3, the variables isSuccess, isError, data, and error are referenced without context. These are properties of the MutationResult object and should be shown as being destructured from the hook or accessed via dot notation to provide accurate guidance to developers.

Suggested change
* // Pattern 3: Use status flags (reactive)
* if (isSuccess) {
* console.log('Created:', data);
* }
* if (isError) {
* console.error('Failed:', error);
* // Pattern 3: Use status flags (reactive) from the mutation result
* const mutation = useCreateVariable();
*
* if (mutation.isSuccess) {
* console.log('Created:', mutation.data);
* }
* if (mutation.isError) {
* console.error('Failed:', mutation.error);

Copilot uses AI. Check for mistakes.
* }
* ```
*
* @typeParam TData - The type of data returned by the mutation.
* @typeParam TPayload - The payload type accepted by the mutation trigger.
*
* @public
*/
export interface MutationResult<TData, TPayload> {
/**
* Trigger the mutation with the given payload.
*
* @returns Promise resolving to the mutation result, or `undefined` if an error occurred
* and `throwOnError` is false. When `throwOnError` is true, errors are thrown instead.
*/
mutate: (payload: TPayload) => Promise<TData | undefined>
/** Current mutation status: 'idle' | 'loading' | 'success' | 'error' */
status: 'idle' | 'loading' | 'success' | 'error'
/** The result data from a successful mutation, or `null` if not yet successful. */
data: TData | null
/** The error from a failed mutation, or `null` if no error. */
error: Error | null
/** `true` while the mutation is in progress. */
isLoading: boolean
/** `true` after a successful mutation. */
isSuccess: boolean
/** `true` after a failed mutation. */
isError: boolean
}
2 changes: 1 addition & 1 deletion src/utils/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function withRetry<T>(
}

// This should never be reached, but TypeScript needs it
/* istanbul ignore next */
/* c8 ignore next */
throw lastError ?? new Error('Retry failed')
}
}
Expand Down