Using Gadget with React

Gadget has React bindings that use the API client generated for Model Testing. These bindings are built on top of the React bindings provided by urql, but with the same type safety guarantees as the generated client.

Installation

To use your Gadget client with React, you must install both React and the React bindings.

npm install @gadgetinc/react react
yarn add @gadgetinc/react react

Client setup

Authentication

The urql client that the Model Testing JS client provides is pre-configured to connect to the right Gadget platform GraphQL endpoint and uses the same authentication mechanism that the outer Gadget client uses. If you configure the Gadget client to use a browser session, the urql client will use the same browser session. If you configure you Gadget client to use API key authentication, the urql client will use that same API key.

React is a client side framework and your code is likely to be visible to all the users of your application. For this reason, Gadget recommends using Session Cookie based authentication for accessing the Gadget API. You shouldn't use an API key that has write permissions for authenticating as that API key will be available to any client and ripe for abuse. You can instead use session-based authentication where users must log in to get privileges, or you can leave the client in the unauthenticated mode and grant permissions to read some backend data to the Unauthenticated role.

See the Authentication documentation for more details on safely giving users access to your application.

Creating the client for React apps

Once your Model Testing package has been installed, create an instance of the Client class in a shared file somewhere:

This client will be shared by all your React components, so it's best to put this code outside any particular component, or within one of your root components where it can be shared. Be sure to select the appropriate authentication mode for your use case.

shared/api
JavaScript
1import { Client } from "@gadget-client/my-blog";
2
3export const api = new Client({
4 authenticationMode: {
5 browserSession: true,
6 },
7});

If you are using Next.js, or other server-side rendering environments, using browserSession authentication mode will not work. Instead, instantiate the client with no options: export const api = new Client({}).

This will default to using the anonymous authentication mode on the server, and the browserSession authentication mode on the client.

Providing the Client

Once your client is installed and set up, you must provide an urql client to your React components using the Gadget React provider. Your Client instance exposes the urql client object at .connection.currentClient.

JavaScript
1import { Provider } from "@gadgetinc/react";
2import { api } from "shared/api";
3const App = () => (
4 <Provider
5 value={api.connection.currentClient}
6 >
7 <YourRoutes />
8 </Provider>
9);

Querying data

You can now use @gadgetinc/react's hooks for fetching models. Gadget provides various hooks for different kinds of queries: useFindOne, useMaybeFindOne, useFindMany, useFindFirst, useMaybeFindFirst, useFindBy, and useGet. These hooks mirror the underlying query methods found on the generated client for Model Testing.

useFindOne, useMaybeFindOne, useFindMany, useFindFirst, useMaybeFindFirst, and useGet take a manager from the API client as the first argument, whereas useFindBy takes a specific findByXYZ method on a manager. All of these methods also take an optional options argument. This is a combination of the options for the relevant query, such as select to choose specific fields, or filters and search for useFindMany, useFindFirst, and useMaybeFindFirst. This options object can also take any of the urql [useQuery] options (except query and variables).

The result of calling these hooks is a two element array, similar to the result of urql's useQuery hook. The first element is the result, composed of an error, fetching status, and the data itself. The second element is a function that you can call to run the query again. By default, urql will use its cache and reach out to the network only when it has been invalidated.

JavaScript
1import { useFindOne } from "@gadgetinc/react";
2import { api } from "shared/client";
3
4const BlogPosts = () => {
5 const [result, refresh] = useFindMany(
6 api.posts,
7 {
8 select: { id: true, title: true },
9 }
10 );
11
12 const { data, fetching, error } =
13 result;
14 if (error)
15 return (
16 <p>Error: {error.message}</p>
17 );
18 if (fetching && !data)
19 return <p>Fetching posts...</p>;
20 if (!data)
21 return (
22 <p>Could not find any posts</p>
23 );
24
25 return (
26 <ul>
27 {data.posts.map((post) => (
28 <li key={post.id}>
29 {post.title}
30 </li>
31 ))}
32 </ul>
33 );
34};

Running actions

The Gadget React bindings provides three options for calling actions: useAction, useBulkAction, and useGlobalAction.

Similar to querying data, these hooks return a two element array. The first element contains all the same result information as queries, but the second element is a function to call the action. This function takes a variables object, along with an optional options argument.

JavaScript
1import { useAction } from "@gadgetinc/react";
2import { useRef } from "react";
3import { api } from "shared/client";
4
5const CreateComment = () => {
6 const commentInputRef = useRef(null);
7 const [result, createComment] =
8 useAction(api.comments.create, {
9 select: { id: true },
10 });
11
12 const { fetching, result, error } =
13 result;
14 return (
15 <form>
16 {error && (
17 <p>Error: {error.message}</p>
18 )}
19 {fetching && (
20 <p>Creating comment...</p>
21 )}
22 <div>
23 <label for="commentBody">
24 Comment body:
25 </label>
26 <input
27 ref={commentInputRef}
28 type="text"
29 id="commentBody"
30 />
31 </div>
32 <button
33 onClick={async (event) => {
34 event.preventDefault();
35 await createComment({
36 comment: {
37 body: commentInputRef
38 .current.value,
39 },
40 });
41 commentInputRef.current.value =
42 "";
43 }}
44 >
45 Create comment
46 </button>
47 </form>
48 );
49};