Preact hooks for use with application APIs built using the Gadget platform.
@gadgetinc/preact is a set of Preact hooks for connecting to a Gadget backend application from a Preact application. There's a few key features:
- Rule-obeying hooks for reading and writing data from a backend that handle all request lifecycle and auth like
useFindOne and useAction
- A full featured, GraphQL-powered nested object selection system using the
select option
- Full type safety for inputs and outputs driven by each Gadget app's backend schema, including over dynamic selections
- Data hydrations that return useful objects like
Dates
This library wraps urql to communicate with a Gadget-generated API, so it benefits from all of
the same features as urql as well!
@gadgetinc/preact is built on top of @gadgetinc/client-hooks, which provides framework-agnostic hook implementations, and @gadgetinc/core, which defines the core types and interfaces for all Gadget clients.
Important Note
@gadgetinc/preact provides a subset of the hooks available in @gadgetinc/react. This package focuses on core data fetching and mutation hooks. For advanced features like authentication components, form helpers (useActionForm), and other React-specific utilities, please use @gadgetinc/react.
The following hooks are available in @gadgetinc/preact:
- Data reading:
useFindOne, useMaybeFindOne, useFindMany, useFindFirst, useMaybeFindFirst, useFindBy, useGet
- Mutations:
useAction, useGlobalAction, useBulkAction
- Background actions:
useEnqueue
- Low-level:
useQuery (as useGadgetQuery), useMutation (as useGadgetMutation), useFetch
- Custom views:
useView
Installation
@gadgetinc/preact is a companion package to the JavaScript client package generated for your Gadget app, so you must install the JS client for your app, and then install this package.
To install the JS client for your app, you must set up the Gadget NPM registry, and then install the client:
npm config set @gadget-client:registry https://registry.gadget.dev/npm
# then
yarn add @gadget-client/example-app-slug
# or
npm install @gadget-client/example-app-slug
Full installation instructions can be found in the Gadget docs at https://docs.gadget.dev/api/<my-app-slug>/external-api-calls/installing.
Once you have your JS client installed, you can install the Preact hooks library with yarn or npm:
yarn add @gadgetinc/preact preact
# or
npm install --save @gadgetinc/preact preact
And finally, you must instantiate an instance of your API client, and then wrap your application in the Provider component exported from this library. In your root-most Preact component, you can add a Provider around the other components of your application:
import { Client } from "@gadget-client/example-app-slug";
import { Provider } from "@gadgetinc/preact";
const api = new Client({ authenticationMode: { browserSession: true } });
export const MyApp = (props) => {
return <Provider api={api}>{props.children}</Provider>;
};
Example usage
import { Client } from "@gadget-client/my-gadget-app";
import { Provider, useAction, useFindMany } from "@gadgetinc/preact";
const api = new Client({ authenticationMode: { browserSession: true } });
export function MyComponent() {
return (
<Provider api={api}>
<WidgetDeleter />
</Provider>
);
}
function WidgetDeleter() {
const [result, refresh] = useFindMany(api.widget, {
select: {
id: true,
name: true,
},
});
const [_, deleteWidget] = useAction(api.widget.delete);
if (result.error) return <>Error: {result.error.toString()}</>;
if (result.fetching && !result.data) return <>Fetching...</>;
if (!result.data) return <>No widgets found</>;
return (
<>
{result.data.map((widget) => (
<button
onClick={(event) => {
event.preventDefault();
void deleteWidget({ id: widget.id }).then(() => refresh());
}}
>
Delete {widget.name}
</button>
))}
</>
);
}
Find more examples in the https://github.com/gadget-inc/examples repo.
Request caching
Under the hood, your Gadget app's API client and @gadgetinc/preact use a powerful, production-grade GraphQL client called urql. urql has a great client-side data caching feature built-in called Document Caching which allows Preact components issuing GraphQL requests for the same data to de-duplicate requests and share client-side state. @gadgetinc/preact enables this functionality by default.
@gadgetinc/preact runs urql's Document Caching with a default requestPolicy of cache-and-network, which means your Preact hooks will re-render data with any cached results from the in-memory store, and then make an underlying HTTP request to fetch the most up to date data.
If you want to change the default requestPolicy that your Gadget API client and Preact hooks use, you can pass the requestPolicy option to your API client constructor.
const api = new Client({
requestPolicy: "network-only",
});
There are four different request policies that you can use:
cache-first prefers cached results and falls back to sending an API request when no prior result is cached.
cache-and-network (the default) returns cached results but also always sends an API request, which is perfect for displaying data quickly while keeping it up-to-date.
network-only will always send an API request and will ignore cached results.
cache-only will always return cached results or null.
For more information on urql's built-in client-side caching, see urql's docs.
Package Architecture
@gadgetinc/preact is part of a layered package architecture:
@gadgetinc/core - Core types and interfaces that all Gadget clients must conform to. Defines AnyClient, GadgetRecord, filter types, etc.
@gadgetinc/client-hooks - Framework-agnostic hook implementations that work across React, Preact, and other frameworks via a runtime adapter pattern.
@gadgetinc/preact - Preact-specific bindings that provide the Provider component and re-export hooks from @gadgetinc/client-hooks configured for Preact.
This architecture allows:
- Shared logic - Query and mutation logic is implemented once in
@gadgetinc/client-hooks and reused across frameworks
- Type safety - All packages share the same type definitions from
@gadgetinc/core
- Framework flexibility - New frameworks can be supported by implementing a runtime adapter for
@gadgetinc/client-hooks
When using @gadgetinc/preact, you only need to install this package - it automatically includes the necessary dependencies.
API Documentation
@gadgetinc/preact contains a variety of Preact hooks for working with your Gadget application's API from a Preact application. Specific code examples for your application's API can be found within your application's docs at https://docs.gadget.dev/api/
Setup
Your Preact application must be wrapped in the Provider component from this library for the hooks to function properly. No other wrappers (like urql's) are necessary.
Available Hooks
The following hooks are available in @gadgetinc/preact. For detailed documentation on each hook's usage, options, and return values, please refer to the @gadgetinc/react documentation, as the APIs are identical:
Data Reading Hooks
useFindOne(manager, id, options) - Fetch one record by ID
useMaybeFindOne(manager, id, options) - Fetch one record by ID, returns null if not found
useFindMany(manager, options) - Fetch a page of records with filtering, sorting, and pagination
useFindFirst(manager, options) - Fetch the first record matching conditions
useMaybeFindFirst(manager, options) - Fetch the first record, returns null if not found
useFindBy(findFunction, fieldValue, options) - Fetch one record by a unique field value
useGet(singletonModelManager, options) - Fetch a singleton record (e.g., api.currentSession)
Mutation Hooks
useAction(actionFunction, options) - Run a model action (create, update, delete, custom actions)
useGlobalAction(globalActionFunction, options) - Run a global action
useBulkAction(bulkActionFunction, options) - Run a bulk action on multiple records
Background Action Hooks
useEnqueue(actionFunction, options) - Enqueue a background action for async execution
Custom View Hooks
useView(viewFunction, variables, options) - Query custom Gelly views
Low-Level Hooks
useGadgetQuery(options) - Run a custom GraphQL query (alias for urql's useQuery)
useGadgetMutation(options) - Run a custom GraphQL mutation (alias for urql's useMutation)
useFetch(path, options) - Make HTTP requests to your Gadget backend's custom routes
Key Features
The select option
The select option allows you to choose which fields and subfields are returned by your Gadget app's GraphQL API. Your app's API supports returning only some fields of each model you request, as well as fields of related models through the Gadget relationship field types.
export const OnlySomeWidgetFields = (props) => {
const [{ data, fetching, error }, _refetch] = useFindOne(api.widget, props.id, { select: { id: true, name: true } });
return (
<span className="widget-name">
{data?.id}: {data?.name}
</span>
);
};
The refetch function
Most read hooks return a refetch function as the second element of the returned array. This function can be called to re-fetch the most up-to-date data from the backend.
const [{ data, fetching, error }, refetch] = useFindMany(api.widget);
void refetch();
Error handling
All hooks return an error object that contains detailed information about any errors that occurred:
error.message: string - A top level error message
error.networkError: Error | undefined - Browser network errors
error.executionErrors: (GraphQLError | GadgetError)[] | undefined - GraphQL API errors
error.validationErrors: { apiIdentifier: string, message: string }[] | undefined - Validation errors from the backend
Suspense support
Data reading hooks support Preact's Suspense for declarative loading states. Pass suspense: true to any read hook:
const Posts = () => {
const [{ data, error }, refresh] = useFindMany(api.post, { suspense: true });
return (
<>
{data.map((post) => (
<Post key={post.id} post={post} />
))}
</>
);
};
<Suspense fallback={<div>Loading...</div>}>
<Posts />
</Suspense>;
Supported on: useFindOne, useMaybeFindOne, useFindMany, useFindFirst, useMaybeFindFirst, useFindBy, useGet, and useView.
Differences from @gadgetinc/react
While @gadgetinc/preact and @gadgetinc/react share the same core data hooks, @gadgetinc/preact does not include:
- ❌ Authentication components (
<SignedIn>, <SignedOut>, <SignedInOrRedirect>, <SignedOutOrRedirect>)
- ❌ Authentication hooks (
useAuth, useUser, useSession, useSignOut)
- ❌
useActionForm hook for form state management
If you need these features, please use @gadgetinc/react instead. The core data hooks work identically in both packages.
Usage with Shopify
@gadgetinc/preact is particularly useful for Shopify applications where bundle size is critical. Preact's smaller footprint makes it ideal for embedded apps and Shopify extensions while maintaining full compatibility with the Gadget API.
TypeScript Support
@gadgetinc/preact is fully typed and benefits from type generation in your Gadget app's API client. All hooks provide full type safety for query inputs, mutation variables, and returned data.
License
MIT
Contributing
Contributions are welcome! Please see the main repository at https://github.com/gadget-inc/js-clients for contribution guidelines.