Skip to content

feat: improve error handling interface + react query #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2025
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

150 changes: 85 additions & 65 deletions .speakeasy/gen.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@ speakeasyVersion: 1.533.0
sources:
my-source:
sourceNamespace: my-source
sourceRevisionDigest: sha256:60981650084fdd89829a7b84dd87e31a97789af8e5af9c431e925f342e1bc19e
sourceRevisionDigest: sha256:a7553f9cadf93d6dec407cf1be5ba62d2dc356c0880236f05599b421725c1871
sourceBlobDigest: sha256:f9444653744aa4a4ca763e60284ec246889d5345d43d6544058196709ead8a39
tags:
- latest
- speakeasy-sdk-regen-1744244404
- 0.4.0
targets:
speakeasy-client-sdk-typescript:
source: my-source
sourceNamespace: my-source
sourceRevisionDigest: sha256:60981650084fdd89829a7b84dd87e31a97789af8e5af9c431e925f342e1bc19e
sourceRevisionDigest: sha256:a7553f9cadf93d6dec407cf1be5ba62d2dc356c0880236f05599b421725c1871
sourceBlobDigest: sha256:f9444653744aa4a4ca763e60284ec246889d5345d43d6544058196709ead8a39
codeSamplesNamespace: my-source-typescript-code-samples
codeSamplesRevisionDigest: sha256:d18a7cac504b265381c2db149e634e5b489f3c747e4a4b4f9eb2e84cee2e3c55
codeSamplesRevisionDigest: sha256:376dffcbce175d84d133f8db2cfe690c1c855afaceb847b61254f129a34b49cd
workflow:
workflowVersion: 1.0.0
speakeasyVersion: latest
Expand Down
3 changes: 1 addition & 2 deletions FUNCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ async function run() {

const { value: result } = res;

// Handle the result
console.log(result);

}

run();
Expand Down
323 changes: 323 additions & 0 deletions REACT_QUERY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
# React hooks

This SDK provides React hooks and utilies for making queries and mutations that
can take the pain out of building front-end applications for the web or React
Native.

They are built as a thin wrapper around [TanStack Query for React v5][rq], a
powerful, asynchronous state management library. A good understanding of that
library will be very helpful while using them. In addition to hooks, there are
several helper functions that can be used for cache management and data fetching
during server-rendering and in React Server Components.

## Getting started

To get started using React hooks, you will need to inject TanStack query and an
SDK instance into your application. Typically, this will be done high up in
your React app at the root or layout component. For example:

```tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SpeakeasyCore } from "@speakeasy-api/speakeasy-client-sdk-typescript";
import { SpeakeasyProvider } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query";

const queryClient = new QueryClient();
const speakeasy = new SpeakeasyCore({
security: {
apiKey: "<YOUR_API_KEY_HERE>",
},
});

// Retries are handled by the underlying SDK.
queryClient.setQueryDefaults(["@speakeasy-api/speakeasy-client-sdk-typescript"], { retry: false });
queryClient.setMutationDefaults(["@speakeasy-api/speakeasy-client-sdk-typescript"], { retry: false });

export function App() {
return (
<QueryClientProvider client={queryClient}>
<SpeakeasyProvider client={speakeasy}>
{/* Your app logic starts here */}
</SpeakeasyProvider>
</QueryClientProvider>
);
}
```

## Queries

Query hooks are the basic building block for fetching data. In addition to
request data, they take the same options as the [`useQuery` hook][use-query]
from TanStack Query.

[use-query]: https://tanstack.com/query/v5/docs/framework/react/reference/useQuery

```tsx
import { useArtifactsGetBlob } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsGetBlob.js";

export function Example() {
const { data, error, status } = useArtifactsGetBlob({
organizationSlug: "<value>",
workspaceSlug: "<value>",
namespaceName: "<value>",
digest: "<value>",
});

// Render the UI here...
}
```

### Query timeouts and retries

Since the underlying SDK handles request timeouts and retries, there are a few
more options provided by the query hooks to control these behaviors.

```tsx
import { useState } from "react";
import { useArtifactsGetBlob } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsGetBlob.js";

export function ExampleWithOptions() {
const [enabled, setEnabled] = useState(true);
const { data, error, status } = useArtifactsGetBlob(
{
organizationSlug: "<value>",
workspaceSlug: "<value>",
namespaceName: "<value>",
digest: "<value>",
},
{
// TanStack Query options:
enabled,
staleTime: 60 * 1000, // 1 minute
gcTime: 5 * 60 * 1000, // 5 minutes

// Request options for the underlying API call:
timeoutMs: 1000,
retryCodes: ["5XX"],
retries: {
strategy: "backoff",
backoff: {
initialInterval: 500,
maxInterval: 10 * 1000, // 10 seconds
exponent: 1.5,
maxElapsedTime: 60 * 1000, // 1 minute
},
},
}
);

// Render the UI here...
}
```


## Mutations

Operations that can have side-effects in this SDK are exposed as mutation hooks.
These can be integrated into HTML forms to submit data to the API. They also
take the same options as the [`useMutation` hook][use-mutation] from TanStack
Query.

[use-mutation]: https://tanstack.com/query/v5/docs/framework/react/reference/useMutation

```tsx
import { useArtifactsCreateRemoteSourceMutation } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsCreateRemoteSource.js";

export function Example() {
const { mutate, status } = useArtifactsCreateRemoteSourceMutation();

return (
<form
onSubmit={(e) => {
e.preventDefault();

// Read form data here...

mutate();
}}
>
{/* Form fields go here... */}
<button type="submit" disabled={status === "pending"}>Submit</button>
</form>
);
}
```

### Mutation timeouts and retries

Since the underlying SDK handles request timeouts and retries, there are a few
more options provided by the mutation hooks to control these behaviors.

```tsx
import { useArtifactsCreateRemoteSourceMutation } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsCreateRemoteSource.js";

export function ExampleWithOptions() {
const { mutate, status } = useArtifactsCreateRemoteSourceMutation({
// TanStack Query options:
networkMode: "online",
gcTime: 5 * 60 * 1000, // 5 minutes

// Request options for the underlying API call:
timeoutMs: 1000,
retryCodes: ["5XX"],
retries: {
strategy: "backoff",
backoff: {
initialInterval: 500,
maxInterval: 10 * 1000, // 10 seconds
exponent: 1.5,
maxElapsedTime: 60 * 1000, // 1 minute
},
},
});

// Render the UI here...
}
```


## Cache invalidation

In many instances, triggering a mutation hook requires invalidating specific
query data currently residing in the TanStack Query's cache. Alongside every
query hook there are two functions that help invalidate cached data:

```tsx
import { useQueryClient } from "@tanstack/react-query";
import { invalidateArtifactsGetBlob, invalidateAllArtifactsGetBlob } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsGetBlob.js";
// Replace this with a real mutation
import { useExampleMutation } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/example.js";

export function Example() {
const { queryClient } = useQueryClient();
const { mutate, status } = useExampleMutation();

return (
<form
onSubmit={(e) => {
e.preventDefault();

const formData = new FormData(e.target);

mutate(formData, {
onSuccess: () => {
// Invalidate a single cache entry:
invalidateArtifactsGetBlob(queryClient, /* ... arguments ... */);
// OR, invalidate all cache entries for the query targets:
invalidateAllArtifactsGetBlob(queryClient);
},
});
}}
>
{/* Form fields go here... */}

<button type="submit" disabled={status === "pending"}>Submit</button>
</form>
);
}
```


## Integration with React Suspense

TanStack Query predates React Suspense and out of the box it does a great job at
exposing the lifecycle of asynchronous tasks. However, if you are already using
Suspense in your app, the default hooks will not trigger suspense boundaries.
This is why the library and, by extension, this SDK also provide equivalent
hooks that integrate neatly with React Suspense.

```tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ErrorBoundary } from "react-error-boundary";

import { SpeakeasyCore } from "@speakeasy-api/speakeasy-client-sdk-typescript";
import { SpeakeasyProvider } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query";
import { useArtifactsGetBlobSuspense } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsGetBlob.js";

const queryClient = new QueryClient();
const speakeasy = new SpeakeasyCore({
security: {
apiKey: "<YOUR_API_KEY_HERE>",
},
});

export function App() {
return (
<QueryClientProvider client={queryClient}>
<SpeakeasyProvider client={speakeasy}>
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
There was an error!{' '}
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
<pre>{error.message}</pre>
</div>
)}
onReset={reset}
>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Example />
</React.Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
</SpeakeasyProvider>
</QueryClientProvider>
);
}

function Example() {
const { data } = useArtifactsGetBlobSuspense({
organizationSlug: "<value>",
workspaceSlug: "<value>",
namespaceName: "<value>",
digest: "<value>",
});

// Render the UI here...
}
```


## Server-rendering and React Server Components

Query hooks are also side-loaded with prefetch helper functions. These functions
can be used to fetch data from the API during server-rendering and in React
Server Components so that it can be available immediately on page load to any
components that use the corresponding hooks:
```tsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { SpeakeasyCore } from "@speakeasy-api/speakeasy-client-sdk-typescript";
import { prefetchArtifactsGetBlob } from "@speakeasy-api/speakeasy-client-sdk-typescript/react-query/artifactsGetBlob.js";

export default async function Page() {
const queryClient = new QueryClient();
const speakeasy = new SpeakeasyCore({
security: {
apiKey: "<YOUR_API_KEY_HERE>",
},
});

await prefetchArtifactsGetBlob(queryClient, speakeasy, {
organizationSlug: "<value>",
workspaceSlug: "<value>",
namespaceName: "<value>",
digest: "<value>",
});

return (
// HydrationBoundary is a Client Component, so hydration will happen there.
<HydrationBoundary state={dehydrate(queryClient)}>
{/* Client components under this point will also have data on page load. */}
</HydrationBoundary>
);
}
```


[rq]: https://tanstack.com/query/v5/docs/framework/react/overview
Loading