Service Function React Hooks
Table of Contents
useQueryService
useQueryService is a hook for managing calls to Magellan service functions using useQuery.
It provides automatic caching, background refetching, and comprehensive state management.
Use it only with services reading data.
Basic Usage
import React, { FC } from "react";
import { useQueryService } from '@quatico/magellan-react';
const MyComponent: FC = () => {
const queryResult = useQueryService({
queryKey: ["users", "1"],
serviceFn: getUserById,
serviceFnArgs: { id: 1 },
});
if (queryResult.isLoading) return <div>Loading...</div>;
if (queryResult.isError) return <div>Error: {queryResult.error.message}</div>;
if (queryResult.isSuccess) return <div>User: {queryResult.data.name}</div>;
return <div>Initializing...</div>;
};
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
queryKey | QueryKey | Yes | This key will be used as the identifier of the cache entry. For documentation see query-keys |
serviceFn | TFn | Yes | The service function to call |
serviceFnArgs | Parameters<TFn>[0] | No | Arguments to pass to the service function (defaults to null) |
enabled | boolean | No | Whether the query runs automatically (defaults to true)For details see useQuery |
Note: When enabled: false, the query will not run automatically. You can still manually trigger the query using the refetch function.
Features
- Automatic Caching based on query key
- Declarative way of refetching in the background base on query key
- Full TypeScript support with discriminated unions
useMutationService
useMutationService is a hook for managing calls to Magellan service functions that modify data.
It provides mutation state management and error handling.
Use it only with services writing data.
Basic Usage
import React, { FC } from "react";
import { useMutationService } from '@quatico/magellan-react';
const CreateUserComponent: FC = () => {
const mutationResult = useMutationService({
queryKey: ["users", "1"],
serviceFn: createUser,
});
const handleCreateUser = async (userData: CreateUserInput) => {
try {
const result = await mutationResult.mutate(userData);
console.log("User created:", result);
} catch (error) {
console.error("Failed to create user:", error);
}
};
return (
<div>
<button
onClick={() => handleCreateUser({ name: "John", email: "john@example.com" })}
disabled={mutationResult.isLoading}
>
{mutationResult.isLoading ? "Creating..." : "Create User"}
</button>
{mutationResult.isError && (
<div>Error: {mutationResult.error.message}</div>
)}
{mutationResult.isSuccess && (
<div>User created successfully!</div>
)}
</div>
);
};
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
mutationKey | MutationKey | Yes | Identify a mutation to share its state like loading, error, or data across components |
serviceFn | TFn | Yes | The service function to call |
Features
- Full TypeScript support with discriminated unions
Best Practices
1. Use Discriminated Unions for Type Safety
// ✅ Good - TypeScript knows data is defined when isSuccess is true
if (queryResult.isSuccess) {
console.log(queryResult.data.name); // data is guaranteed to be defined
}
// ❌ Avoid - data might be undefined
console.log(queryResult.data?.name);
2. Handle All States Explicitly
// ✅ Good - Handle all possible states
const MyComponent = () => {
const queryResult = useQueryService({...});
if (queryResult.isLoading) return <LoadingSpinner />;
if (queryResult.isError) return <ErrorMessage error={queryResult.error} />;
if (queryResult.isSuccess) return <DataDisplay data={queryResult.data} />;
return <InitializingMessage />;
};
3. Use Appropriate Query Keys
Further reading: Effective React Query Keys
// ✅ Good - Descriptive and hierarchical
const queryKey = ["users", userId, "profile"];
// ❌ Avoid - Generic or unclear
const queryKey = ["data"];
4. Handle Errors Gracefully
// ✅ Good - Provide retry mechanism
const handleError = (error: Error) => {
console.error("Query failed:", error);
// Show user-friendly error message
// Provide retry option
};
5. Use Mutations for Data Modifications
// ✅ Good - Use mutation for creating data
const createUserMutation = useMutationService({
mutationKey: ["createUser"],
serviceFn: createUser,
});
// ❌ Avoid - Using query for mutations
const createUserQuery = useQueryService({
queryKey: ["createUser"],
serviceFn: createUser, // This should be a mutation
});
6. Leverage React Query Features
// ✅ Good - Use enabled for conditional queries
const queryResult = useQueryService({
queryKey: ["user", userId],
serviceFn: getUser,
enabled: !!userId, // Only run when userId exists
});
Common Patterns
Loading States
const LoadingExample = () => {
const queryResult = useQueryService({...});
return (
<div>
{queryResult.isLoading && <div>Loading...</div>}
{queryResult.isError && <div>Error occurred</div>}
{queryResult.isSuccess && <div>Data loaded successfully</div>}
</div>
);
};
Error Boundaries
const ErrorHandlingExample = () => {
const queryResult = useQueryService({...});
if (queryResult.isError) {
return (
<div className="error-container">
<h2>Something went wrong</h2>
<p>{queryResult.error.message}</p>
<button onClick={() => queryResult.refetch()}>
Try Again
</button>
</div>
);
}
// ... rest of component
};
Integration Examples
Complete Query Example
import React, { FC, useEffect, useState } from "react";
import { useQueryService } from "@quatico/magellan-react";
interface TestQueryComponentProps {
queryKey: string[];
serviceFn: (input: any) => Promise<any>;
serviceFnArgs?: any;
enabled?: boolean;
onResultChange?: (result: any) => void;
}
const TestQueryComponent: FC<TestQueryComponentProps> = ({
queryKey,
serviceFn,
serviceFnArgs,
enabled = true,
onResultChange,
}) => {
const queryResult = useQueryService({
queryKey,
serviceFn,
serviceFnArgs,
enabled,
});
const [lastRefetchResult, setLastRefetchResult] = useState<any>(null);
useEffect(() => {
onResultChange?.(queryResult);
}, [queryResult, onResultChange]);
const handleRefetch = async () => {
try {
const result = await queryResult.refetch?.();
setLastRefetchResult((result as any)?.data);
} catch (error) {
setLastRefetchResult({ error: error instanceof Error ? error.message : String(error) });
}
};
return (
<div>
<div data-testid="loading">{queryResult.isLoading ? "Loading" : "Idle"}</div>
<div data-testid="error">{queryResult.isError ? "Error" : "No Error"}</div>
<div data-testid="success">{queryResult.isSuccess ? "Success" : "Not Success"}</div>
<div data-testid="last-result">
{queryResult.data === undefined ? "undefined" : JSON.stringify(queryResult.data)}
</div>
<div data-testid="error-message">{queryResult.error?.message || "No Error"}</div>
<div data-testid="query-state">
{queryResult.isLoading && "Loading"}
{queryResult.isError && "Error"}
{queryResult.isSuccess && "Success"}
</div>
<button data-testid="refetch-button" onClick={handleRefetch} disabled={queryResult.isLoading}>
Refetch
</button>
</div>
);
};
Complete Mutation Example
import React, { FC, useEffect, useState } from "react";
import { useMutationService } from "@quatico/magellan-react";
interface TestMutationComponentProps {
mutationKey: string[];
serviceFn: (input: any) => Promise<any>;
onResultChange?: (result: any) => void;
}
const TestMutationComponent: FC<TestMutationComponentProps> = ({
mutationKey,
serviceFn,
onResultChange
}) => {
const mutationResult = useMutationService({
mutationKey,
serviceFn,
});
const [inputValue, setInputValue] = useState("");
useEffect(() => {
onResultChange?.(mutationResult);
}, [mutationResult, onResultChange]);
const handleMutate = async () => {
try {
const result = await mutationResult.mutate({ input: inputValue });
console.log("Mutation result:", result);
} catch (error) {
console.error("Mutation error:", error);
}
};
return (
<div>
<input
data-testid="mutation-input"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="Enter input"
/>
<button
data-testid="mutate-button"
onClick={handleMutate}
disabled={mutationResult.isLoading}
>
{mutationResult.isLoading ? "Mutating..." : "Mutate"}
</button>
<div data-testid="loading">{mutationResult.isLoading ? "Loading" : "Idle"}</div>
<div data-testid="error">{mutationResult.isError ? "Error" : "No Error"}</div>
<div data-testid="success">{mutationResult.isSuccess ? "Success" : "Not Success"}</div>
<div data-testid="last-result">
{mutationResult.data === undefined ? "undefined" : JSON.stringify(mutationResult.data)}
</div>
<div data-testid="error-message">{mutationResult.error?.message || "No Error"}</div>
</div>
);
};