Skip to main content
Version: v0.16.2

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

ParameterTypeRequiredDescription
queryKeyQueryKeyYesThis key will be used as the identifier of the cache entry.
For documentation see query-keys
serviceFnTFnYesThe service function to call
serviceFnArgsParameters<TFn>[0]NoArguments to pass to the service function (defaults to null)
enabledbooleanNoWhether 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

ParameterTypeRequiredDescription
mutationKeyMutationKeyYesIdentify a mutation to share its state like loading, error, or data across components
serviceFnTFnYesThe 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>
);
};