@gadgetinc/react 

The @gadgetinc/react package provides React hooks for calling your Gadget app's auto-generated API.

React
1import { useFindMany, useAction } from "@gadgetinc/react";
2
3// your app's auto-generated API client
4import { api } from "../api";
5
6function WidgetDeleter() {
7 // `useFindMany` executes a backend fetch to get a list of widgets from the backend.
8 const [{ data, fetching, error }, refresh] = useFindMany(api.widget, {
9 select: {
10 id: true,
11 name: true,
12 },
13 });
14
15 // `useAction` sets up a mutation we can run to delete a specific widget when a user clicks a button
16 const [_, deleteWidget] = useAction(api.widget.delete);
17
18 if (fetching) return "Loading...";
19
20 return (
21 <ul>
22 {data?.map((widget) => (
23 <li>
24 {widget.name}
25 <button onClick={() => deleteWidget({ id: widget.id })}>Delete</button>
26 </li>
27 ))}
28 </ul>
29 );
30}
1import { useFindMany, useAction } from "@gadgetinc/react";
2
3// your app's auto-generated API client
4import { api } from "../api";
5
6function WidgetDeleter() {
7 // `useFindMany` executes a backend fetch to get a list of widgets from the backend.
8 const [{ data, fetching, error }, refresh] = useFindMany(api.widget, {
9 select: {
10 id: true,
11 name: true,
12 },
13 });
14
15 // `useAction` sets up a mutation we can run to delete a specific widget when a user clicks a button
16 const [_, deleteWidget] = useAction(api.widget.delete);
17
18 if (fetching) return "Loading...";
19
20 return (
21 <ul>
22 {data?.map((widget) => (
23 <li>
24 {widget.name}
25 <button onClick={() => deleteWidget({ id: widget.id })}>Delete</button>
26 </li>
27 ))}
28 </ul>
29 );
30}

Key features 

  1. Rule-obeying hooks for reading and writing data from a backend that handle all request lifecycle and auth like useFindOne, useAction, and useFetch
  2. Full type safety for inputs and outputs driven by each Gadget app's backend schema, including over dynamic selections
  3. A full-featured, GraphQL-powered nested object selection system using the select option
  4. Data hydrations that return useful objects like Date

Installation 

See the installation instructions for your app's API.

Provider Setup 

Your React 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.

Gadget provisions applications with the required <Provider /> component already in place! If using the frontend hosting built into Gadget, no action is required as this step is already done.

Example:

React
1// import the API client for your specific application from your client package, be sure to replace this package name with your own
2import { Client } from "@gadget-client/example-app-slug";
3// import the required Provider object and some example hooks from this package
4import { Provider } from "@gadgetinc/react";
5
6// instantiate the API client for our app
7const api = new Client({ authenticationMode: { browserSession: true } });
8
9export const MyApp = (props: { children: React.ReactNode }) => {
10 // wrap the application in the <Provider> so the hooks can find the current client
11 return <Provider api={api}>{props.children}</Provider>;
12};
1// import the API client for your specific application from your client package, be sure to replace this package name with your own
2import { Client } from "@gadget-client/example-app-slug";
3// import the required Provider object and some example hooks from this package
4import { Provider } from "@gadgetinc/react";
5
6// instantiate the API client for our app
7const api = new Client({ authenticationMode: { browserSession: true } });
8
9export const MyApp = (props: { children: React.ReactNode }) => {
10 // wrap the application in the <Provider> so the hooks can find the current client
11 return <Provider api={api}>{props.children}</Provider>;
12};

Client side vs Server side React 

The @gadgetinc/react package is intended for use on the client-side only. The hooks provided by this package make requests from your app's frontend directly to your Gadget app's backend from the browser. If you want to use server-side rendering, you can use your framework's server-side data loader support and make imperative calls with your API client object.

Hooks 

useFindOne() 

useFindOne(manager: ModelFinder, id: string, options: SingleFinderOptions = {}): [{data, fetching, error}, refetch]

useFindOne fetches one record from your Gadget database with a given id.

React
1import React from "react";
2import { useFindOne } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ShowWidgetName = (props: { id: string }) => {
6 const [{ data, fetching, error }, _refetch] = useFindOne(api.widget, props.id);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return <span className="widget-name">{data?.name}</span>;
11};
1import React from "react";
2import { useFindOne } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ShowWidgetName = (props: { id: string }) => {
6 const [{ data, fetching, error }, _refetch] = useFindOne(api.widget, props.id);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return <span className="widget-name">{data?.name}</span>;
11};
Parameters 
  • manager: The model manager for the model you want to find a record of. Required. Example: api.widget, or api.shopifyProduct
  • id: The backend id of the record you want to find. Required.
  • options: Options for making the call to the backend. Not required, and all keys are optional.
    • select: A list of fields and subfields to select. See select option
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Set to true to disable this hook. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense
Returns 

useFindOne returns two values: a result object with the data, fetching, and error keys for inspecting in your React component's output, and a refetch function to trigger a refresh of the hook's data.

  • data: GadgetRecord | null: The record fetched from the backend. Is null while the data is being loaded, or if the record wasn't found.
  • fetching: boolean: A boolean describing if the hook is currently requesting data from the backend.
  • error: Error | null: An error from the client or server side, if encountered during the request. Will contain an error if the record isn't found by id. See the errors section.

useFindOne expects a record with the given id to be found in the backend database, and will return an error in the error property if no record with this id is found.

useFindOne can select only some fields from the backend model with the select option:

React
1import React from "react";
2import { useFindOne } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const OnlySomeWidgetFields = (props: { id: string }) => {
6 // fetch only the widget id and name fields
7 const [{ data, fetching, error }, _refetch] = useFindOne(api.widget, props.id, {
8 select: {
9 id: true,
10 name: true,
11 },
12 });
13
14 return (
15 <span className="widget-name">
16 {data?.id}: {data?.name}
17 </span>
18 );
19};
1import React from "react";
2import { useFindOne } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const OnlySomeWidgetFields = (props: { id: string }) => {
6 // fetch only the widget id and name fields
7 const [{ data, fetching, error }, _refetch] = useFindOne(api.widget, props.id, {
8 select: {
9 id: true,
10 name: true,
11 },
12 });
13
14 return (
15 <span className="widget-name">
16 {data?.id}: {data?.name}
17 </span>
18 );
19};

useMaybeFindOne() 

useMaybeFindOne(manager: ModelFinder, id: string, options: SingleFinderOptions = {}): [{data, fetching, error}, refetch]

useMaybeFindOne fetches one record from your Gadget database with a given id.

React
1import React from "react";
2import { useMaybeFindOne } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ShowWidgetName = (props: { id: string }) => {
6 const [{ data, fetching, error }, _refetch] = useMaybeFindOne(api.widget, props.id);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 if (data) {
11 return <span className="widget-name">{data.name}</span>;
12 } else {
13 return "No widget found";
14 }
15};
1import React from "react";
2import { useMaybeFindOne } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ShowWidgetName = (props: { id: string }) => {
6 const [{ data, fetching, error }, _refetch] = useMaybeFindOne(api.widget, props.id);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 if (data) {
11 return <span className="widget-name">{data.name}</span>;
12 } else {
13 return "No widget found";
14 }
15};

useMaybeFindOne will return data: null and error: null if no record with the given id is found in the backend database. useMaybeFindOne otherwise behaves identically to useFindOne, and accepts the same options.

useFindMany() 

useFindMany(manager: ModelFinder, options: ManyFinderOptions = {}): [{data, fetching, error}, refetch]

useFindMany fetches a page of records from your Gadget database, optionally sorted, filtered, or searched.

React
1import React from "react";
2import { useFindMany } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ShowWidgetNames = () => {
6 const [{ data, fetching, error }, _refetch] = useFindMany(api.widget);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return (
11 <ul>
12 {data?.map((widget) => (
13 <li key={widget.id}>{widget.name}</li>
14 ))}
15 </ul>
16 );
17};
1import React from "react";
2import { useFindMany } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ShowWidgetNames = () => {
6 const [{ data, fetching, error }, _refetch] = useFindMany(api.widget);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return (
11 <ul>
12 {data?.map((widget) => (
13 <li key={widget.id}>{widget.name}</li>
14 ))}
15 </ul>
16 );
17};
Parameters 
  • manager: The model manager for the model you want to find a page of records for. Required. Example: api.widget, or api.shopifyProduct
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs.
    • filter: A list of filters to limit the set of returned records. Optional. See the Model Filtering section in your application's API documentation to see the available filters for your models.
    • search: A search string to match backend records against. Optional. See the Model Searching section in your application's API documentation to see the available search syntax.
    • sort: A sort order to return backend records by. Optional. See the sorting section in your application's API documentation for more info.
    • first & after: Pagination arguments to pass to fetch a subsequent page of records from the backend. first should hold a record count and after should hold a string cursor retrieved from the pageInfo of the previous page of results. See the pagination section in your application's API documentation for more info.
    • last & before: Pagination arguments to pass to fetch a subsequent page of records from the backend. last should hold a record count and before should hold a string cursor retrieved from the pageInfo of the previous page of results. See the pagination section in your application's API documentation for more info.
    • live: Should this hook re-render when data changes on the backend. See the live option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense for more info
Returns 

useFindMany returns two values: a result object with the data, fetching, and error keys for use in your React component's output, and a refetch function to trigger a refresh of the hook's data.

  • data: GadgetRecordList | null: The resulting page of records fetched from the backend for your model, once they've arrived
  • fetching: boolean: A boolean describing if the hook is currently making a request to the backend.
  • error: Error | null: An error from the client or server side, if encountered during the request. See the errors section.

Without any options, useFindMany will fetch the first page of backend records sorted by id.

useFindMany accepts the select option to allow customization of which fields are returned:

React
1import React from "react";
2import { useFindMany } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const OnlySomeWidgetFields = () => {
6 // fetch only the widget id and name fields
7 const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
8 select: {
9 id: true,
10 name: true,
11 },
12 });
13
14 if (fetching) return "Loading...";
15 if (error) return `Error loading data: ${error}`;
16 return (
17 <ul>
18 {data?.map((widget) => (
19 <li key={widget.id}>{widget.name}</li>
20 ))}
21 </ul>
22 );
23};
1import React from "react";
2import { useFindMany } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const OnlySomeWidgetFields = () => {
6 // fetch only the widget id and name fields
7 const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
8 select: {
9 id: true,
10 name: true,
11 },
12 });
13
14 if (fetching) return "Loading...";
15 if (error) return `Error loading data: ${error}`;
16 return (
17 <ul>
18 {data?.map((widget) => (
19 <li key={widget.id}>{widget.name}</li>
20 ))}
21 </ul>
22 );
23};

useFindMany accepts a filter option to limit which records are returned from the backend. For example, we can filter to return only widgets created since the start of 2022:

React
1// fetch only the widgets created recently
2const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
3 filter: {
4 createdAt: { greaterThan: new Date(2022, 1, 1) },
5 },
6});
1// fetch only the widgets created recently
2const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
3 filter: {
4 createdAt: { greaterThan: new Date(2022, 1, 1) },
5 },
6});

See your app's API reference for more information on which filters are available on what models.

useFindMany accepts a sort option to change the order of the records that are returned. For example, we can sort returned widgets by the createdAt field:

React
1// return the most recently created widgets first
2const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
3 sort: {
4 createdAt: "Descending",
5 },
6});
1// return the most recently created widgets first
2const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
3 sort: {
4 createdAt: "Descending",
5 },
6});

useFindMany accepts a search option to limit the fetched records to only those matching a given search query. For example, we can search all the backend widgets for those matching the string "penny" in any searchable field:

React
// return widgets with "penny" in any searchable field
const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
search: "penny",
});
// return widgets with "penny" in any searchable field
const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
search: "penny",
});

See your app's API reference for more information on the search query syntax and which fields are searchable.

useFindMany accepts a live option to subscribe to changes in the backend data returned, which will trigger re-renders of your react components as that data changes. For example, we can show an up-to-date view of the first page of backend widgets:

React
// will update when new widgets are created or on-screen widgets are updated
const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
live: true,
});
// will update when new widgets are created or on-screen widgets are updated
const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
live: true,
});

useFindMany accepts pagination arguments for getting the second, third, etc page of results from the backend beyond just the first page. Gadget applications use Relay Cursor style GraphQL pagination, where a second page is fetched by asking for the next x many results after a cursor returned with the first page.

React
1// return the first 10 results after some cursor from somewhere else
2const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
3 first: 10,
4 after: "some-cursor-value",
5});
6
7// data is a GadgetRecordList object, which has extra properties for inquiring about the pagination state
8// the current page's start and end cursor are available for use to then make later requests for different pages
9const {
10 // string used for forward pagination, pass to the `after:` variable
11 endCursor,
12 // string used for backwards pagination, pass to the `before:` variable
13 startCursor,
14
15 // `data` also reports if there are more pages for fetching
16 // boolean indicating if there is another page to fetch after the `endCursor`
17 hasNextPage,
18 // boolean indicating if there is another page to fetch before the `startCursor`
19 hasPreviousPage,
20} = data;
1// return the first 10 results after some cursor from somewhere else
2const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
3 first: 10,
4 after: "some-cursor-value",
5});
6
7// data is a GadgetRecordList object, which has extra properties for inquiring about the pagination state
8// the current page's start and end cursor are available for use to then make later requests for different pages
9const {
10 // string used for forward pagination, pass to the `after:` variable
11 endCursor,
12 // string used for backwards pagination, pass to the `before:` variable
13 startCursor,
14
15 // `data` also reports if there are more pages for fetching
16 // boolean indicating if there is another page to fetch after the `endCursor`
17 hasNextPage,
18 // boolean indicating if there is another page to fetch before the `startCursor`
19 hasPreviousPage,
20} = data;

An easy way to do pagination is using React state, or for a better user experience, using the URL with whatever router system works for your application. We use React state to demonstrate pagination in this example:

React
1import React, { useState } from "react";
2import { useFindMany } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const WidgetPaginator = () => {
6 // store the current cursor in the backend data
7 const [cursor, setCursor] = useState<string | undefined>(undefined);
8
9 // pass the current cursor to the `after:` variable, telling the backend to return data after this cursor
10 const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
11 first: 20,
12 after: cursor,
13 });
14
15 if (fetching) return "Loading...";
16 if (error) return `Error loading data: ${error}`;
17
18 return (
19 <>
20 <ul>
21 {data?.map((widget) => (
22 <li key={widget.id}>{widget.name}</li>
23 ))}
24 </ul>
25 <button
26 onClick={() => {
27 // update the cursor, which will trigger a refetch of the next page and rerender with a new `data` object
28 setCursor(data?.endCursor);
29 }}
30 disabled={!data?.hasNextPage}
31 >
32 Next page
33 </button>
34 </>
35 );
36};
1import React, { useState } from "react";
2import { useFindMany } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const WidgetPaginator = () => {
6 // store the current cursor in the backend data
7 const [cursor, setCursor] = useState<string | undefined>(undefined);
8
9 // pass the current cursor to the `after:` variable, telling the backend to return data after this cursor
10 const [{ data, fetching, error }, _refetch] = useFindMany(api.widget, {
11 first: 20,
12 after: cursor,
13 });
14
15 if (fetching) return "Loading...";
16 if (error) return `Error loading data: ${error}`;
17
18 return (
19 <>
20 <ul>
21 {data?.map((widget) => (
22 <li key={widget.id}>{widget.name}</li>
23 ))}
24 </ul>
25 <button
26 onClick={() => {
27 // update the cursor, which will trigger a refetch of the next page and rerender with a new `data` object
28 setCursor(data?.endCursor);
29 }}
30 disabled={!data?.hasNextPage}
31 >
32 Next page
33 </button>
34 </>
35 );
36};

useFindFirst() 

useFindFirst(manager: ModelFinder, options: FindManyOptions = {}): [{data, fetching, error}, refetch]

useFindFirst fetches the first record from your backend Gadget database that matches the given filter, sort, and search.

React
1import React from "react";
2import { useFindFirst } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const MostRecentPublishedPost = (props: { id: string }) => {
6 // request the first record from the backend where publishedAt is set and with the most recent publishedAt value
7 const [{ data, fetching, error }, _refetch] = useFindFirst(api.blogPost, {
8 filter: { publishedAt: { isSet: true } },
9 sort: { publishedAt: "Descending" },
10 });
11
12 if (fetching) return "Loading...";
13 if (error) return `Error loading data: ${error}`;
14 return <span className="banner">Check out our most recent blog post titled {data?.title}</span>;
15};
1import React from "react";
2import { useFindFirst } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const MostRecentPublishedPost = (props: { id: string }) => {
6 // request the first record from the backend where publishedAt is set and with the most recent publishedAt value
7 const [{ data, fetching, error }, _refetch] = useFindFirst(api.blogPost, {
8 filter: { publishedAt: { isSet: true } },
9 sort: { publishedAt: "Descending" },
10 });
11
12 if (fetching) return "Loading...";
13 if (error) return `Error loading data: ${error}`;
14 return <span className="banner">Check out our most recent blog post titled {data?.title}</span>;
15};
Parameters 
  • manager: The model manager for the model you want to find a page of records for. Required. Example: api.widget, or api.shopifyProduct
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs. Optional.
    • filter: A list of filters to find a record matching. Optional. See the Model Filtering section in your application's API documentation to see the available filters for your models.
    • search: A search string to find a record matching. Optional. See the Model Searching section in your application's API documentation to see the available search syntax.
    • sort: A sort order to order the backend records by. useFindFirst will only return the first record matching the given search and filter, so sort can be used to break ties and select a specific record. Optional. See the sorting section in your application's API documentation for more info.
    • live: Should this hook re-render when data changes on the backend. See the live option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense for more info
Returns 

useFindFirst returns two values: a result object with the data, fetching, and error keys for inspecting in your React component's output, and a refetch function to trigger a refresh of the hook's data.

  • data: GadgetRecord | null: The record fetched from the backend. Is null while the data is being loaded, or if a matching record wasn't found.
  • fetching: boolean: A boolean describing if the hook is currently making a request to the backend.
  • error: Error | null: An error from the client or server side, if encountered during the request. Will contain an error if the first record isn't found. See the errors section.

If no record is found matching the conditions, useFindFirst will return {data: null, error: new MissingDataError}.

Without any options, useFindFirst will fetch the first matching record and cause your component to rerender as the fetch happens and when the data or error arrives.

useFindFirst can only select some of the fields from the backend model with select:

React
1// fetch the first upside down widget, and only it's id and name fields
2const [{ data }] = useFindFirst(api.widget, {
3 filter: {
4 state: { equals: "upsideDown" },
5 },
6 select: {
7 id: true,
8 name: true,
9 },
10});
1// fetch the first upside down widget, and only it's id and name fields
2const [{ data }] = useFindFirst(api.widget, {
3 filter: {
4 state: { equals: "upsideDown" },
5 },
6 select: {
7 id: true,
8 name: true,
9 },
10});

useFindFirst can subscribe to changes in the returned data from the backend with the live option, and re-render when the backend data changes:

React
1// fetch the first upside down widget, and re-render if it's data changes
2const [{ data }] = useFindFirst(api.widget, {
3 filter: {
4 state: { equals: "upsideDown" },
5 },
6 live: true,
7});
1// fetch the first upside down widget, and re-render if it's data changes
2const [{ data }] = useFindFirst(api.widget, {
3 filter: {
4 state: { equals: "upsideDown" },
5 },
6 live: true,
7});

useFindBy() 

useFindBy(findFunction: ModelFindFunction, fieldValue: any, options: FindByOptions = {}): [{data, fetching, error}, refetch]

useFindBy fetches one record from your backend looked up by a specific field and value. useFindBy requires a by-field record finder like .findBySlug or .findByEmail to exist for your model, which are generated by adding a Unique Validations to a field.

React
1import React from "react";
2import { useFindBy } from "@gadgetinc/react";
3import { api } from "../api";
4
5// get a slug from the URL or similar, and look up a post record by this slug
6export const PostBySlug = (props: { slug: string }) => {
7 const [{ data, fetching, error }, _refetch] = useFindBy(api.blogPost.findBySlug, props.slug);
8
9 if (fetching) return "Loading...";
10 if (error) return `Error loading data: ${error}`;
11 return (
12 <>
13 <h2>{data?.title}</h2>
14 <p>{data?.body}</p>
15 </>
16 );
17};
1import React from "react";
2import { useFindBy } from "@gadgetinc/react";
3import { api } from "../api";
4
5// get a slug from the URL or similar, and look up a post record by this slug
6export const PostBySlug = (props: { slug: string }) => {
7 const [{ data, fetching, error }, _refetch] = useFindBy(api.blogPost.findBySlug, props.slug);
8
9 if (fetching) return "Loading...";
10 if (error) return `Error loading data: ${error}`;
11 return (
12 <>
13 <h2>{data?.title}</h2>
14 <p>{data?.body}</p>
15 </>
16 );
17};
Parameters 
  • findFunction: The model finder function from your application's API client for finding records by a specific field. Gadget generates these finder functions for the fields where they are available. Changes to your Gadget backend schema may be required to get these to exist. Required. Example: api.widget.findBySlug, or api.user.findByEmail.
  • fieldValue: The value of the field to search for a record using. This is which slug or email you'd pass to api.widget.findBySlug or api.user.findByEmail.
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs.
    • live: Should this hook re-render when data changes on the backend. See the live option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense for more info
Returns 

useFindBy returns two values: a result object with the data, fetching, and error keys for inspecting in your React component's output, and a refetch function to trigger a refresh of the hook's data.

  • data: GadgetRecord | null: The record fetched from the backend. Is null while the data is being loaded, or if a matching record wasn't found for the given fieldValue.
  • fetching: boolean: A boolean describing if the hook is currently making a request to the backend.
  • error: Error | null: An error from the client or server side, if encountered during the request. Will contain an error if a matching record isn't found. See the errors section.

If no record is found matching the conditions, then the returned object will have null for the data. useFindBy(api.widget.findByEmail, "[email protected]") is the React equivalent of api.widget.findByEmail("[email protected]")

Without any options, useFindBy will fetch the record with the given field value, and cause your component to rerender as the fetch happens and when the data or error arrives:

React
1import React from "react";
2import { useFindBy } from "@gadgetinc/react";
3import { api } from "../api";
4
5// get a slug from the URL or similar, and look up a post record by this slug
6export const PostBySlug = (props: { slug: string }) => {
7 const [{ data, fetching, error }, _refetch] = useFindBy(api.blogPost.findBySlug, props.slug);
8
9 if (fetching) return "Loading...";
10 if (error) return `Error loading data: ${error}`;
11 return (
12 <>
13 <h2>{data?.title}</h2>
14 <p>{data?.body}</p>
15 </>
16 );
17};
1import React from "react";
2import { useFindBy } from "@gadgetinc/react";
3import { api } from "../api";
4
5// get a slug from the URL or similar, and look up a post record by this slug
6export const PostBySlug = (props: { slug: string }) => {
7 const [{ data, fetching, error }, _refetch] = useFindBy(api.blogPost.findBySlug, props.slug);
8
9 if (fetching) return "Loading...";
10 if (error) return `Error loading data: ${error}`;
11 return (
12 <>
13 <h2>{data?.title}</h2>
14 <p>{data?.body}</p>
15 </>
16 );
17};

useFindBy can take options that allow the customization of which fields are returned:

React
// fetch only a post id and title fields
const [{ data, fetching, error }, _refetch] = useFindBy(api.blogPost.findBySlug, "some-slug", { select: { id: true, title: true } });
// fetch only a post id and title fields
const [{ data, fetching, error }, _refetch] = useFindBy(api.blogPost.findBySlug, "some-slug", { select: { id: true, title: true } });

The refetch function returned as the second element can be executed in order to trigger a refetch of the most up to date data from the backend. See urql's docs on re-executing queries for more information.

useMaybeFindFirst() 

useMaybeFindFirst(manager: ModelFinder, options: FindManyOptions = {}): [{data, fetching, error}, refetch]

useMaybeFindFirst fetches the first record from your backend Gadget database that matches the given filter, sort, and search parameters.

React
1import React from "react";
2import { useMaybeFindFirst } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const MostRecentPublishedPost = (props: { id: string }) => {
6 // request the first record from the backend where publishedAt is set and with the most recent publishedAt value
7 const [{ data, fetching, error }, _refetch] = useMaybeFindFirst(api.blogPost, {
8 filter: { publishedAt: { isSet: true } },
9 sort: { publishedAt: "Descending" },
10 });
11
12 if (fetching) return "Loading...";
13 if (error) return `Error loading data: ${error}`;
14 if (data) {
15 return <span className="banner">Check out our most recent blog post titled {data.title}</span>;
16 } else {
17 // no first record found
18 return null;
19 }
20};
1import React from "react";
2import { useMaybeFindFirst } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const MostRecentPublishedPost = (props: { id: string }) => {
6 // request the first record from the backend where publishedAt is set and with the most recent publishedAt value
7 const [{ data, fetching, error }, _refetch] = useMaybeFindFirst(api.blogPost, {
8 filter: { publishedAt: { isSet: true } },
9 sort: { publishedAt: "Descending" },
10 });
11
12 if (fetching) return "Loading...";
13 if (error) return `Error loading data: ${error}`;
14 if (data) {
15 return <span className="banner">Check out our most recent blog post titled {data.title}</span>;
16 } else {
17 // no first record found
18 return null;
19 }
20};

useMaybeFindFirst returns data: null if no record is found in the backend database, and otherwise works identically to useFindFirst. See useFindFirst for more details on the options useMaybeFindFirst accepts.

useAction() 

useAction(actionFunction: ModelActionFunction, options: UseActionOptions = {}): [{data, fetching, error}, refetch]

React
1import React from "react";
2import { useAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CreatePost = () => {
6 const [{ data, fetching, error }, createBlogPost] = useAction(api.blogPost.create);
7
8 return (
9 <button
10 onClick={() => {
11 createBlogPost({
12 title: "New post created from React",
13 });
14 }}
15 >
16 Create a post
17 </button>
18 );
19};
1import React from "react";
2import { useAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CreatePost = () => {
6 const [{ data, fetching, error }, createBlogPost] = useAction(api.blogPost.create);
7
8 return (
9 <button
10 onClick={() => {
11 createBlogPost({
12 title: "New post created from React",
13 });
14 }}
15 >
16 Create a post
17 </button>
18 );
19};

useAction is a hook for running a backend action on one record of a Gadget model. useAction must be passed an action function from an instance of your application's generated API client. Options:

Parameters 
  • actionFunction: The model action function from your application's API client for acting on records. Gadget generates these action functions for each action defined on backend Gadget models. Required. Example: api.widget.create, or api.user.update or api.blogPost.publish.
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense for more info
Returns 

useAction returns two values: a result object with the data, fetching, and error keys for inspecting in your React component's output, and a act function to actually run the backend action. useAction is a rule-following React hook that wraps action execution, which means it doesn't just run the action as soon as the hook is invoked. Instead, useAction returns a configured function that will actually run the action, which you need to call in response to some user event. The act function accepts the action inputs as arguments -- not useAction itself.

useAction's result will return the data, fetching, and error details for the most recent execution of the action.

  • data: GadgetRecord | null: The record fetched from the backend after a mutation. Is null while before the mutation is run and while it is currently ongoing.
  • fetching: boolean: A boolean describing if the hook is currently making a request to the backend.
  • error: Error | null: An error from the client or server side, if encountered during the mutation. Will contain an error if the client passed invalid data, if the server failed to complete the action, or if a network error was encountered. See the errors section.

For example, we can create a button that creates a post when clicked, and then shows the post once it has been created:

React
1import React from "react";
2import { useAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CreatePost = () => {
6 const [{ data, fetching, error }, createBlogPost] = useAction(api.blogPost.create);
7
8 // deal with all the states of the result object
9 if (fetching) return "Loading...";
10 if (error) return `Error loading data: ${error}`;
11
12 if (!data) {
13 return (
14 <button
15 onClick={() => {
16 createBlogPost({
17 title: "New post created from React",
18 });
19 }}
20 >
21 Create a post
22 </button>
23 );
24 } else {
25 return (
26 <>
27 <h2>{data.title}</h2>
28 <p>{data.body}</p>
29 </>
30 );
31 }
32};
1import React from "react";
2import { useAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CreatePost = () => {
6 const [{ data, fetching, error }, createBlogPost] = useAction(api.blogPost.create);
7
8 // deal with all the states of the result object
9 if (fetching) return "Loading...";
10 if (error) return `Error loading data: ${error}`;
11
12 if (!data) {
13 return (
14 <button
15 onClick={() => {
16 createBlogPost({
17 title: "New post created from React",
18 });
19 }}
20 >
21 Create a post
22 </button>
23 );
24 } else {
25 return (
26 <>
27 <h2>{data.title}</h2>
28 <p>{data.body}</p>
29 </>
30 );
31 }
32};

We can also run actions on existing models by passing the id: in with the action parameters:

React
1import React, { useState } from "react";
2import { useAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const UpdatePost = (props: { id: string }) => {
6 // invoke the `useAction` hook, getting back a result object and an action runner function every render
7 const [{ data, fetching, error }, updateBlogPost] = useAction(api.blogPost.update);
8 const [title, setTitle] = useState("");
9
10 if (fetching) return "Loading...";
11 if (error) return `Error loading data: ${error}`;
12
13 return (
14 <form
15 onSubmit={() => {
16 // pass the id of the blog post we're updating as one parameter, and the new post attributes as another
17 updateBlogPost({
18 id: props.id,
19 title,
20 });
21 }}
22 >
23 <label>Title</label>
24 <input type="text" value={title} onChange={(event) => setTitle(event.target.value)} />
25 <input type="submit">Submit</input>
26 </form>
27 );
28};
1import React, { useState } from "react";
2import { useAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const UpdatePost = (props: { id: string }) => {
6 // invoke the `useAction` hook, getting back a result object and an action runner function every render
7 const [{ data, fetching, error }, updateBlogPost] = useAction(api.blogPost.update);
8 const [title, setTitle] = useState("");
9
10 if (fetching) return "Loading...";
11 if (error) return `Error loading data: ${error}`;
12
13 return (
14 <form
15 onSubmit={() => {
16 // pass the id of the blog post we're updating as one parameter, and the new post attributes as another
17 updateBlogPost({
18 id: props.id,
19 title,
20 });
21 }}
22 >
23 <label>Title</label>
24 <input type="text" value={title} onChange={(event) => setTitle(event.target.value)} />
25 <input type="submit">Submit</input>
26 </form>
27 );
28};

useAction can take options that allow the customization of which fields are returned on the acted-upon record:

React
// fetch only a post id and title fields
const [{ data, fetching, error }, updateBlogPost] = useAction(api.blogPost.update, { select: { id: true, title: true } });
// fetch only a post id and title fields
const [{ data, fetching, error }, updateBlogPost] = useAction(api.blogPost.update, { select: { id: true, title: true } });

useGlobalAction() 

useGlobalAction(actionFunction: GlobalActionFunction, options: UseGlobalActionOptions = {}): [{data, fetching, error}, refetch]

useGlobalAction is a hook for running a backend Global Action.

React
1import React from "react";
2import { useGlobalAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const PurgeData = () => {
6 const [{ data, fetching, error }, runPurge] = useGlobalAction(api.purgeData);
7
8 return (
9 <button
10 onClick={() => {
11 runPurge({ foo: "bar" });
12 }}
13 >
14 Purge data
15 </button>
16 );
17};
1import React from "react";
2import { useGlobalAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const PurgeData = () => {
6 const [{ data, fetching, error }, runPurge] = useGlobalAction(api.purgeData);
7
8 return (
9 <button
10 onClick={() => {
11 runPurge({ foo: "bar" });
12 }}
13 >
14 Purge data
15 </button>
16 );
17};
Parameters 
  • globalActionFunction: The action function from your application's API client. Gadget generates these global action functions for each global action defined in your Gadget backend. Required. Example: api.runSync, or api.purgeData (corresponding to Global Actions named Run Sync or Purge Data).
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
Returns 

useGlobalAction returns two values: a result object with the data, fetching, and error keys for inspecting in your React component's output, and an act function to actually run the backend global action. useGlobalAction is a rule-following React hook that wraps action execution, which means it doesn't just run the action as soon as the hook is invoked. Instead, useGlobalAction returns a configured function which you need to call in response to some event. This act function accepts the action inputs as arguments. useGlobalAction's result will return the data, fetching, and error details for the most recent execution of the action.

  • data: Record&lt;string, any&gt; | null: The data returned by the global action from the backend. Is null while before the mutation is run and while it is currently ongoing.
  • fetching: boolean: A boolean describing if the hook is currently making a request to the backend.
  • error: Error | null: An error from the client or server side, if encountered during the mutation. Will contain an error if the client passed invalid data, if server failed to complete the mutation, or if a network error was encountered. See the errors section.

For example, we can create a button that runs a Global Action called purgeData when clicked, and shows the result after it has been run:

React
1import React from "react";
2import { useGlobalAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const PurgeData = () => {
6 const [{ data, fetching, error }, runPurge] = useGlobalAction(api.purgeData);
7
8 // deal with all the states of the result object
9 if (fetching) return "Loading...";
10 if (error) return `Error running global action: ${error}`;
11
12 if (!data) {
13 return (
14 <button
15 onClick={() => {
16 runPurge({ foo: "bar" });
17 }}
18 >
19 Purge data
20 </button>
21 );
22 } else {
23 return "Purge completed";
24 }
25};
1import React from "react";
2import { useGlobalAction } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const PurgeData = () => {
6 const [{ data, fetching, error }, runPurge] = useGlobalAction(api.purgeData);
7
8 // deal with all the states of the result object
9 if (fetching) return "Loading...";
10 if (error) return `Error running global action: ${error}`;
11
12 if (!data) {
13 return (
14 <button
15 onClick={() => {
16 runPurge({ foo: "bar" });
17 }}
18 >
19 Purge data
20 </button>
21 );
22 } else {
23 return "Purge completed";
24 }
25};

useGet() 

useGet(singletonModelManager: SingletonModelManager, options: GetOptions = {}): [{data, fetching, error}, refetch]

useGet fetches a singleton record for an api.currentSomething style model manager. useGet fetches one global record, which is most often the current session.

If you'd like to access the current session on the frontend, use the useSession() hook

React
1import React from "react";
2import { useGet } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CurrentSessionId = () => {
6 const [{ data, fetching, error }, _refetch] = useGet(api.currentSession);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return <span>Current session: {data?.id}</span>;
11};
1import React from "react";
2import { useGet } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CurrentSessionId = () => {
6 const [{ data, fetching, error }, _refetch] = useGet(api.currentSession);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return <span>Current session: {data?.id}</span>;
11};
Parameters 
  • singletonModelManager: The singleton model manager available on the generated API client for your application. The passed model manager must be one of the currentSomething model managers. useGet can't be used with other model managers that don't have a .get function. Example: api.currentSession.
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense for more info
Returns 

useGet returns two values: a result object with the data, fetching, and error keys for inspecting in your React component's output, and a refetch function to trigger a refresh of the hook's data.

  • data: GadgetRecord | null: The record fetched from the backend. Is null while the data is being loaded, or if the record wasn't found.
  • fetching: boolean: A boolean describing if the hook is currently making a request to the backend.
  • error: Error | null: An error from the client or server side, if encountered during the request. Will contain an error if the singleton record isn't found. See the errors section.

useGet(api.currentSession) retrieves the current global session for the current browser

React
1import React from "react";
2import { useGet } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CurrentSessionId = () => {
6 const [{ data, fetching, error }, _refetch] = useGet(api.currentSession);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return <span>Current session: {data?.id}</span>;
11};
1import React from "react";
2import { useGet } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const CurrentSessionId = () => {
6 const [{ data, fetching, error }, _refetch] = useGet(api.currentSession);
7
8 if (fetching) return "Loading...";
9 if (error) return `Error loading data: ${error}`;
10 return <span>Current session: {data?.id}</span>;
11};

useGet can take options which allow the customization of which fields are returned on the selected record:

React
1// fetch only the session id and state fields
2const [{ data, fetching, error }, _refetch] = useGet(api.currentSession, {
3 select: {
4 id: true,
5 createdAt: true,
6 },
7});
1// fetch only the session id and state fields
2const [{ data, fetching, error }, _refetch] = useGet(api.currentSession, {
3 select: {
4 id: true,
5 createdAt: true,
6 },
7});

The refetch function returned as the second element can be executed to trigger a refetch of the most up-to-date data from the backend. See urql's docs on re-executing queries for more information.

useEnqueue() 

useEnqueue(actionFunction: ModelActionFunction || GlobalActionFunction, actionInput, backgroundActionOptions = {}): [{error, fetching, handle}, enqueue]

useEnqueue facilitates the enqueuing of actions to be executed in the background.

web/components/CreateUserButton.jsx
React
1import React from "react";
2import { useEnqueue } from "@gadgetinc/react";
3import { api } from "../api";
4
5export function CreateUserButton(props: { name: string; email: string }) {
6 const [{ error, fetching, handle }, enqueue] = useEnqueue(api.user.create);
7
8 const onClick = () =>
9 enqueue(
10 {
11 // actionInput
12 name: props.name,
13 email: props.email,
14 },
15 {
16 // backgroundActionoptions
17 id: `send-email-action-${props.email}`,
18 }
19 );
20
21 return (
22 <>
23 {error && <>Failed to enqueue user create: {error.toString()}</>}
24 {fetching && <>Enqueuing action...</>}
25 {handle && <>Enqueued action with background action id={handle.id}</>}
26 <button onClick={onClick}>Create user</button>
27 </>
28 );
29}
1import React from "react";
2import { useEnqueue } from "@gadgetinc/react";
3import { api } from "../api";
4
5export function CreateUserButton(props: { name: string; email: string }) {
6 const [{ error, fetching, handle }, enqueue] = useEnqueue(api.user.create);
7
8 const onClick = () =>
9 enqueue(
10 {
11 // actionInput
12 name: props.name,
13 email: props.email,
14 },
15 {
16 // backgroundActionoptions
17 id: `send-email-action-${props.email}`,
18 }
19 );
20
21 return (
22 <>
23 {error && <>Failed to enqueue user create: {error.toString()}</>}
24 {fetching && <>Enqueuing action...</>}
25 {handle && <>Enqueued action with background action id={handle.id}</>}
26 <button onClick={onClick}>Create user</button>
27 </>
28 );
29}
Parameters 
  • actionFunction: ModelActionFunction || GlobalActionFunction: Either a model or global action.
  • actionInput: Input | null: The parameters or data passed to a model or global action.
  • backgroundActionOptions (optional): The options for how the background action runs, for more information check out the reference here.
Returns 

useEnqueue doesn't submit the background action when invoked but instead returns a function for enqueuing the action in response to the event.

useActionForm() 

useActionForm(actionFunction: ModelActionFunction, options: UseActionFormOptions = {}): UseActionFormResult

useActionForm manages form state for calling actions in your Gadget backend. useActionForm can fetch the record for editing, manage the state of the fields as the user changes them in a form, track validations and errors, and then call the action with the form data when the user submits the form. useActionForm can call Actions on models as well as Global Actions.

useActionForm wraps the excellent react-hook-form library and provides all the same state management primitives that react-hook-form does, in addition to Gadget-specific goodies like automatic record fetching, automatic action calling and type safety.

React
1import React from "react";
2import { useActionForm } from "@gadgetinc/react";
3import { api } from "../api";
4
5const PostForm = () => {
6 const { register, submit } = useActionForm(api.post.create);
7
8 return (
9 <form onSubmit={submit}>
10 <label htmlFor="title">Title</label>
11 <input id="title" type="text" {...register("post.title")} />
12 <label htmlFor="content">Content</label>
13 <textarea id="content" {...register("post.content")} />
14 <input type="submit" />
15 </form>
16 );
17};
1import React from "react";
2import { useActionForm } from "@gadgetinc/react";
3import { api } from "../api";
4
5const PostForm = () => {
6 const { register, submit } = useActionForm(api.post.create);
7
8 return (
9 <form onSubmit={submit}>
10 <label htmlFor="title">Title</label>
11 <input id="title" type="text" {...register("post.title")} />
12 <label htmlFor="content">Content</label>
13 <textarea id="content" {...register("post.content")} />
14 <input type="submit" />
15 </form>
16 );
17};

Read more about building forms in the Frontend Forms guide.

Parameters 
  • action: the Model Action or Global Action to call when submitting. Required.
  • options: the configuration for the form

The options options object accepts the following options:

NameTypeDescription
defaultValuesPartial<ActionInput>Default values to seed the form inputs with. Can be omitted. Mutually exclusive with the findBy. option.
findBystring or { [field: string]: any }Details for automatically finding a record to seed the form values with. When passed as a string, will look up a record with that id. When passed an object, will call a findBy<Field> function on the api object to retrieve a record by that field. The field must have a uniqueness validation in order to function.
mode"onChange" or "onBlur" or "onSubmit" or "onTouched" or "all"Validation strategy before submitting behavior
reValidateMode"onChange" or "onBlur" or "onSubmit"Validation strategy after submitting behavior
resetOptionsResetOptionsDefault options to use when calling reset. For more details, see the reset function docs
criteriaMode"firstError" or "all"Display all validation errors or one at a time.
shouldFocusErrorbooleanEnable or disable built-in focus management.
delayErrornumberDelay errors by this many milliseconds to avoid them appearing instantly
shouldUseNativeValidationbooleanUse browser built-in form constraint API.
shouldUnregisterbooleanEnable and disable input unregister after unmount.
selectRecordSelectionWhich fields to select from the backend when retrieving initial data with findBy. Can also mark fields as ReadOnly to exclude them from being sent during an update. See docs on the select option for more information.
sendstring[]Which fields to send from the form values to the backend for the action. Useful if you want to include fields in your form state for driving UI that shouldn't be sent with the submission
onSubmit() => voidCallback called right before data is submitted to the backend action
onSuccess(actionResult: ActionResultData) => voidCallback called after a successful submission to the backend action. Passed the action result, which is the object with {data, error, fetching} keys
onError(error: Error | FieldErrors) => voidCallback called after an error occurs finding the initial record or during submission to the backend action. Passed the error, which can be a transport error from a broken network, or a list of validation errors returned by the backend

useActionForm's props input is very similar to useForm's from react-hook-form. For more docs on these props, see the react-hook-form docs.

Returns 

useActionForm returns a variety of functions and state for managing your form that most users destructure as they call it:

React
const { register, submit, error, reset, ...rest } = useActionForm(api.post.update, {
defaultValues: {
id: "123",
},
});
const { register, submit, error, reset, ...rest } = useActionForm(api.post.update, {
defaultValues: {
id: "123",
},
});

The available result values are:

NameTypeDescription
registerFunctionA function for registering uncontrolled inputs with the form docs
unregisterFunctionA function for de-registering uncontrolled inputs with the form. Needed when dynamically adjusting the form elements on screen. docs
formStateFormStateCurrent state of the form, like validations, errors, submission details, etc docs
submit(event?: React.Event) => Promise<ActionResult>A function to call that submits the form to the backend. Returns a promise for the ActionResult object containing the {data, error, fetching} triple returned by the backend action.
watch(names?: string | string[] | (data, options) => void) => unknownA function for observing form values for easily changing how a form is displayed. docs
reset<T>(values?: T | ResetAction<T>, options?: Record<string, boolean>) => voidFunction for resetting the entire form state to specific values. docs
resetField(name: string, options?: Record<string, boolean | any>) => voidFunction for resetting one field in the values to a specific value. docs
setError(name: string, error: FieldError, { shouldFocus?: boolean }) => voidFunction for manually adding errors to fields. docs
clearErrors(name?: string | string[]) => voidFunction for removing all the errors or errors on one field. docs
setValue(name: string, value: unknown, config?: Object) => voidFunction for setting one field to a specific value. docs
setFocus(name: string, options: SetFocusOptions) => voidFunction for imperatively focusing one field. docs
getValues(payload?: string | string[]) => ObjectFunction for retrieving all the values from within the form state. docs
getFieldState(name: string, formState?: Object) => ({isDirty, isTouched, invalid, error})Function for returning the state of one individual field from within the form state. docs
trigger(name?: string | string[]) => Promise<boolean>Manually triggers form or input validation. This method is also useful when you have dependant validation (input validation depends on another input's value). docs
controlFormControlContext object for passing to <Controller/> components wrapping ref-less or controlled components
errorError | nullAny top-level Error objects encountered during processing. Will contain transport level errors as well as field validation errors returned by the backend. This value is for deeply inspecting the error if you want more than just the message, but the formState.errors object should be preferred if not.
actionData{data: Result | null, error: Error | null, fetching: false }The ActionResult triple returned by the inner useAction hook. Will be populated with the action execution result after submission.

FormState object 

The FormState object returned by useActionForm includes the following properties:

NameTypeDescription
isDirtybooleantrue if the user has modified any inputs away from the defaultValues, and false otherwise
dirtyFieldsRecord<string, boolean>A map of fields to the dirty state for each field. Each field's property on the object true if the user has modified this field away from the default and false otherwise
touchedFieldsRecord<string, boolean>A map of fields to the touched state for each field. Each field's property on the object true if the user has modified this field at all and false otherwise
defaultValuesRecord<string, any>The default values the form started out with, or has been reset to
isSubmittedbooleantrue if the form has ever been submitted, false otherwise
isSubmitSuccessfulbooleantrue if the form has completed a submission that encountered no errors, false otherwise
isSubmittingbooleantrue if the form is currently submitting to the backend, false otherwise
isLoadingbooleantrue if the form is currently loading data from the backend to populate the initial values or another input, false otherwise
submitCountnumberCount of times the form has been submitted
isValidbooleantrue if the form has no validation errors currently, false otherwise
isValidatingbooleantrue if the form is currently validating data, false otherwise
errorsRecord<string, string>A map of any validation errors currently present on each field

The FormState object managed by useActionForm (and react-hook-form underneath) is a Proxy object that tracks which properties are accessed during rendering to avoid excessive re-renders. Make sure you read its properties within a component render function to properly track which properties should trigger re-renders.

For more on this proxy object, see the react-hook-form docs

For more on the FormState object, see the react-hook-form docs.

Missing options from react-hook-form 

Unlike react-hook-form, useActionForm manages the submission process to your backend action. You don't need to manually make a call to your action -- instead, call the submit function returned by useActionForm when you are ready to submit the form, and useActionForm will call the action.

Because the submission process is managed, useActionForm does not accept the handleSubmit option that react-hook-form does.

<Controller/> 

useActionForm's register function only works with uncontrolled components that conform to normal DOM APIs. For working with controlled components, like those from popular UI libraries such as @shopify/polaris, you must register these input components with a <Controller/> instead.

React
1import { TextField } from "@shopify/polaris";
2import { useActionForm, Controller } from "@gadgetinc/react";
3import { api } from "../api";
4
5const AllowedTagForm = () => {
6 const { submit, control } = useActionForm(api.allowedTag.create);
7
8 return (
9 <form onSubmit={submit}>
10 <Controller
11 name="keyword"
12 control={control}
13 render={({ field }) => {
14 // Functional components like the Polaris TextField do not allow for 'ref's to be passed in
15 // Remove it from the props passed to the TextField
16 const { ref, ...fieldProps } = field;
17 // Pass the field props down to the textField to set the value value and add onChange handlers
18 return <TextField label="Keyword" type="text" autoComplete="off" {...fieldProps} value={fieldProps.value ?? undefined} />;
19 }}
20 />
21 </form>
22 );
23};
1import { TextField } from "@shopify/polaris";
2import { useActionForm, Controller } from "@gadgetinc/react";
3import { api } from "../api";
4
5const AllowedTagForm = () => {
6 const { submit, control } = useActionForm(api.allowedTag.create);
7
8 return (
9 <form onSubmit={submit}>
10 <Controller
11 name="keyword"
12 control={control}
13 render={({ field }) => {
14 // Functional components like the Polaris TextField do not allow for 'ref's to be passed in
15 // Remove it from the props passed to the TextField
16 const { ref, ...fieldProps } = field;
17 // Pass the field props down to the textField to set the value value and add onChange handlers
18 return <TextField label="Keyword" type="text" autoComplete="off" {...fieldProps} value={fieldProps.value ?? undefined} />;
19 }}
20 />
21 </form>
22 );
23};

<Controller/> accepts the following props:

NameTypeDescription
namestring | FieldPathThe key of this input's data within your form's field values
controlFormControlContext object returned by useActionForm, must be passed to each <Controller/> to tie them to the form
render(props: ControllerProps) => ReactElementA function that returns a React element and provides the ability to attach events and value into the component. This simplifies integrating with external controlled components with non-standard prop names. Provides onChange, onBlur, name, ref and value as props for sending to the child component, and also a fieldState object which contains specific input state.
defaultValueunknownA default value for applying to the inner input.
rulesobjectValidation options for applying to the form value. Accepts the same format of options as the register function.
shouldUnregisterbooleanShould this input's values/validations/errors be removed on unmount.
disabledbooleanIs this input currently disabled such that it can't be edited

For more information on <Controller/>, see the react-hook-form docs

useFieldArray() 

useFieldArray(fieldArrayProps: UseFieldArrayProps): UseFieldArrayResult

useActionForm also supports the useFieldArray hook from react-hook-form for managing form state on dynamic forms and for related models.

For examples of using useFieldArray to manage related models, see the Forms guide.

useFieldArray accepts the following props:

NameTypeDescription
namestringRequired. Name of the field array
controlFormControlContext object returned by useActionForm, must be passed to useFieldArray to tie array state to the form
rulesobjectValidation options for applying to the form value. Accepts the same format of options as the register function
shouldUnregisterbooleanShould this input's values/validations/errors be removed on unmount
Returns 

useFieldArray returns the managed form state, and a variety of functions for manipulating the array:

NameTypeDescription
fieldsobject & { id: string }Contains the default value and key for component array
append(obj: object | object[], focusOptions) => voidAppend input to the end of your fields and focus. The input value will be registered during this action
prepend(obj: object | object[], focusOptions) => voidPrepend input to the start of your fields and focus. The input value will be registered during this action
insert(index: number, value: object | object[], focusOptions) => voidInsert at particular position and focus
swap (from: number, to: number) => voidSwap array element's position
move (from: number, to: number) => voidMove array element to another position
update(index: number, obj: object) => voidUpdate input at a particular position. The updated fields will get unmounted and remounted, if this is not the desired behavior, use setValue API instead
replace (obj: object[]) => voidReplace all values currently in the field array
remove (index?: number | number[]) => voidRemove input at a particular position or remove all when no index provided

useList() 

useList(manager: ModelFinder, options?: ManyFinderOptions = {}): [{ data, fetching, page, search, error }, refresh]

useList handles the logic of creating a paginated, searchable list of records from your Gadget backend. The useList hook takes the same parameters as useFindMany (with the addition of the pageSize param). Refer to useFindMany for examples of how to query the data that will populate your list.

Parameters 
  • manager: The model manager for the model you want to find a page of records for. Required. Example: api.widget, or api.shopifyProduct
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs.
    • filter: A list of filters to limit the set of returned records. Optional. See filtering in your application's API documentation for more info.
    • search: A search string to match backend records against. Optional. See the model searching section in your application's API documentation for the available search syntax.
    • sort: A sort order to return backend records by. Optional. See sorting in your application's API documentation for more info.
    • first & after: Pagination arguments to pass to fetch a subsequent page of records from the backend. first should hold a record count and after should hold a string cursor retrieved from the pageInfo of the previous page of results. See the pagination section in your application's API documentation for more info.
    • last & before: Pagination arguments to pass to fetch a subsequent page of records from the backend. last should hold a record count and before should hold a string cursor retrieved from the pageInfo of the previous page of results. See the pagination section in your application's API documentation for more info.
    • live: Should this hook re-render when data changes on the backend. See the live option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs.
    • pause: Should the hook make a request right now or not. See urql's docs.
    • suspense: Should this hook suspend when fetching data. See suspense for more info.
    • pageSize: The page size of the paginated data. Optional, defaults to 50.
Returns 

useList returns two values: a result object with the data, fetching, page, search, and error keys for use in your React component's output, and a refresh function to trigger a refresh of the hook's data.

  • data: GadgetRecord | null: The record fetched from the backend. Is null while the data is being loaded, or if the record wasn't found.
  • fetching: boolean: A boolean describing if the hook is currently requesting data from the backend.
  • page: PaginationResult: A collection of variables and functions to handle pagination.
    • page.hasNextPage: boolean | undefined: Whether the paginated data has a next page.
    • page.hasPreviousPage: boolean | undefined: Whether the paginated data has a previous page.
    • page.variables: { first?: number; after?: string; last?: number; before?: string; }:
      • first holds a record count and after holds a string cursor retrieved from the pageInfo of the previous page of results. See the pagination section in your application's API documentation for more info.
      • last holds a record count and before holds a string cursor retrieved from the pageInfo of the previous page of results. See the pagination section in your application's API documentation for more info.
    • page.pageSize: number: Page size of the paginated data. Defaults to 50.
    • page.goToNextPage: () => void: Function to load the next page of paginated data.
    • page.goToPreviousPage: () => void: Function to load the last page of paginated data.
  • search: SearchResult: A collection of variables and functions to handle pagination.
    • value: string: The current value of the input, possibly changing rapidly as the user types.
    • debouncedValue: string: The value that has been processed by the debounce function, updating less frequently than value. Learn more about debouncing.
    • set: (value: string) => void: A function to update the value.
    • clear: () => void: A function to clear the value.
  • error: Error | null: An error from the client or server side, if encountered during the request. Will contain an error if the record isn't found by id. See the errors section.

Paginated list 

Here's how you can create a paginated list that is synced with your data in Gadget:

pagination example with useList
React
1import { useList } from "@gadgetinc/react";
2// your app's auto-generated API client
3import { api } from "../api";
4
5export const PaginatedList = () => {
6 // Passing the same filter/sort/search params to useList as useFindMany:
7 const [{ data, fetching, error, page }] = useList(api.customer, { sort: { createdAt: "Descending" } });
8
9 if (fetching) return "Loading list...";
10 if (error) return `An error occurred: ${error.message}`;
11
12 return (
13 <>
14 {/* easy pagination! */}
15 <button onClick={() => page.goToPreviousPage()} disabled={!page.hasPreviousPage}>
16 Previous
17 </button>
18 <button onClick={() => page.goToNextPage()} disabled={!page.hasNextPage}>
19 Next
20 </button>
21 <ul>
22 {data?.map((customer) => (
23 <li key={customer.id}>{customer.email}</li>
24 ))}
25 </ul>
26 </>
27 );
28};
1import { useList } from "@gadgetinc/react";
2// your app's auto-generated API client
3import { api } from "../api";
4
5export const PaginatedList = () => {
6 // Passing the same filter/sort/search params to useList as useFindMany:
7 const [{ data, fetching, error, page }] = useList(api.customer, { sort: { createdAt: "Descending" } });
8
9 if (fetching) return "Loading list...";
10 if (error) return `An error occurred: ${error.message}`;
11
12 return (
13 <>
14 {/* easy pagination! */}
15 <button onClick={() => page.goToPreviousPage()} disabled={!page.hasPreviousPage}>
16 Previous
17 </button>
18 <button onClick={() => page.goToNextPage()} disabled={!page.hasNextPage}>
19 Next
20 </button>
21 <ul>
22 {data?.map((customer) => (
23 <li key={customer.id}>{customer.email}</li>
24 ))}
25 </ul>
26 </>
27 );
28};

Searchable list 

The fun continues! The useList hook also supports search:

example of searching with useList
React
1import { useList } from "@gadgetinc/react";
2// your app's auto-generated API client
3import { api } from "../api";
4
5export const SearchableList = () => {
6 const [{ data, fetching, error, search }] = useList(api.customer);
7
8 const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
9 event.preventDefault();
10 search.set(event.target.value);
11 };
12
13 if (error) return `An error occurred: ${error.message}`;
14
15 return (
16 <>
17 <input onChange={handleSearch} />
18 <button onClick={search.clear}>Clear</button>
19 {fetching ? (
20 <p>Loading list...</p>
21 ) : (
22 <ul>
23 {data?.map((customer) => (
24 <li key={customer.id}>{customer.email}</li>
25 ))}
26 </ul>
27 )}
28 </>
29 );
30};
1import { useList } from "@gadgetinc/react";
2// your app's auto-generated API client
3import { api } from "../api";
4
5export const SearchableList = () => {
6 const [{ data, fetching, error, search }] = useList(api.customer);
7
8 const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
9 event.preventDefault();
10 search.set(event.target.value);
11 };
12
13 if (error) return `An error occurred: ${error.message}`;
14
15 return (
16 <>
17 <input onChange={handleSearch} />
18 <button onClick={search.clear}>Clear</button>
19 {fetching ? (
20 <p>Loading list...</p>
21 ) : (
22 <ul>
23 {data?.map((customer) => (
24 <li key={customer.id}>{customer.email}</li>
25 ))}
26 </ul>
27 )}
28 </>
29 );
30};

useTable() 

useTable(manager: ModelFinder, options?: ManyFinderOptions = {}): [{ data, fetching, page, search, error }, refresh]

useTable is a headless React hook for powering a table. The table shows a page of Gadget records from the backend. The hook returns an object with optional params for sorting, filtering, searching, and data selection. useTable returns an object with data, fetching, and error keys, and a refetch function. data is a GadgetRecordList object holding the list of returned records and pagination info.

This hook is like useList. The difference is it divides each field into columns, and orders the records into rows.

React
1import React from "react";
2import { useTable } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ExampleTable = () => {
6 const [{ rows, columns, fetching, error }] = useTable(api.customer);
7
8 if (fetching) return "Loading table...";
9 if (error) return `There was an error loading the table: ${error.message}`;
10
11 return (
12 <table>
13 <thead>
14 <tr>
15 {/* Create a column header for each column name in `columns` */}
16 {columns?.map((col) => (
17 <th key={col.identifier}>{col.header}</th>
18 ))}
19 </tr>
20 </thead>
21 <tbody>
22 {/* Create a row in the HTML table for each `row` in `rows` */}
23 {rows?.map((row) => (
24 <tr key={`${row.id}`}>
25 {/* Index the row object using the column API identifier to populate each cell */}
26 {columns.map((col) => (
27 <td key={col.identifier}>{String(row[col.identifier])}</td>
28 ))}
29 </tr>
30 ))}
31 </tbody>
32 </table>
33 );
34};
1import React from "react";
2import { useTable } from "@gadgetinc/react";
3import { api } from "../api";
4
5export const ExampleTable = () => {
6 const [{ rows, columns, fetching, error }] = useTable(api.customer);
7
8 if (fetching) return "Loading table...";
9 if (error) return `There was an error loading the table: ${error.message}`;
10
11 return (
12 <table>
13 <thead>
14 <tr>
15 {/* Create a column header for each column name in `columns` */}
16 {columns?.map((col) => (
17 <th key={col.identifier}>{col.header}</th>
18 ))}
19 </tr>
20 </thead>
21 <tbody>
22 {/* Create a row in the HTML table for each `row` in `rows` */}
23 {rows?.map((row) => (
24 <tr key={`${row.id}`}>
25 {/* Index the row object using the column API identifier to populate each cell */}
26 {columns.map((col) => (
27 <td key={col.identifier}>{String(row[col.identifier])}</td>
28 ))}
29 </tr>
30 ))}
31 </tbody>
32 </table>
33 );
34};
Parameters 
  • manager: The model manager for the model you want to find a page of records for. Required. Example: api.widget, or api.shopifyProduct
  • options: Options for making the call to the backend. Not required and all keys are optional.
    • select: A list of fields and subfields to select. See the select option docs.
    • filter: A list of filters to limit the set of returned records. Optional. See the model filtering section in your application's API documentation to see the available filters for your models.
    • search: A search string to match backend records against. Optional. See the model searching section in your application's API documentation to see the available search syntax.
    • live: Should this hook re-render when data changes on the backend. See the live option docs.
    • requestPolicy: The urql request policy to make the request with. See urql's docs
    • pause: Should the hook make a request right now or not. See urql's docs
    • suspense: Should this hook suspend when fetching data. See suspense for more info
    • pageSize: The page size of the paginated data. Optional, defaults to 50.
    • initialCursor: A string cursor; useTable handles pagination, so this prop is only needed if you get the cursor hash from the returned after or before variables, and want to set the cursor to a custom spot.
    • initialSort: An object of type { [column: string]: "Ascending" | "Descending" } that sets the initial sort order for the table.
    • initialDirection: Initial pagination direction. Either "forward" or "backward".
    • columns: A list of field API identifiers and custom column renderer objects to be returned. Custom cell renderers have type {header: string; render: (props: {record: GadgetRecord<any>, index: number}) => ReactNode; style?: React.CSSProperties;}
columns prop example
React
// Only returns the `name` and `createdAt` columns
const [{ rows, columns, fetching, error, page }] = useTable(api.customer, {
columns: ["name", "createdAt"],
});