# Building frontends
## Reading data from models
Gadget provides the [`@gadgetinc/react`](https://npmjs.com/package/@gadgetinc/react) library of React hooks for reading and writing data from your Gadget models. The `useFindOne`, `useFindMany`, `useFindBy`, and `useGet` hooks read data from your backend models and render UI with React.
Each of these hooks returns an object with the requested `data`, the `fetching` state, and an `error` if one was encountered, as well as a `refetch` function for refreshing the data if need be.
For example, if you have a model named `post`, we can fetch post records in a variety of ways:
```tsx
// fetch one post by id
const [{ data, fetching, error }, refetch] = useFindOne(api.post, "10");
// fetch the first 10 posts
const [{ data, fetching, error }, refetch] = useFindMany(api.post, { first: 10 });
// fetch the first post with the slug field equal to "example-slug", throw if it isn't found
const [{ data, fetching, error }, refetch] = useFindFirst(api.post, { where: { slug: "example-slug" } });
// fetch the first post with the slug field equal to "example-slug", return null if it isn't found
const [{ data, fetching, error }, refetch] = useMaybeFindFirst(api.post, { where: { slug: "example-slug" } });
// fetch a realtime-updated view of the first 10 posts from the backend
const [{ data, fetching, error }, refetch] = useFindMany(api.post, { live: true, first: 10 });
// fetch the user's current session
const [{ data, fetching, error }, refetch] = useGet(api.currentSession);
```
Each of these hooks must be wrapped in a React component to render. For example, we can use `useFindMany` to display a list of posts in a component:
```tsx
import { useFindMany } from "@gadgetinc/react";
import { ReactNode } from "react";
import { api } from "../api";
export const PostsList = () => {
const [{ data, fetching, error }, _refetch] = useFindMany(api.post, { first: 10 });
if (fetching) {
return
Loading...
;
}
if (error) {
return
Error: {error.message}
;
}
return (
{data.map((post) => (
{post.title}
))}
);
};
```
For more documentation on the `@gadgetinc/react` hooks library, see the [React reference](https://docs.gadget.dev/reference/react).
## Writing data to models
The [`@gadgetinc/react`](https://npmjs.com/package/@gadgetinc/react) React hooks library includes the [`useActionForm` hook](https://docs.gadget.dev/guides/frontend/forms) and the [`useAction` hook](https://docs.gadget.dev/reference/react#useaction) for writing data to models by running [Actions](https://docs.gadget.dev/guides/actions). `useActionForm` is suitable for building forms that give users inputs to control the input data, and `useAction` is a lower level hook for calling actions directly.
### Building forms
With the `useActionForm` hook, you can manage form state and call actions easily. For example, we can build a form for a `post` model with `useActionForm`:
```tsx
import { useActionForm } from "@gadgetinc/react";
import { api } from "../api";
const PostForm = () => {
const { register, submit } = useActionForm(api.post.create);
return (
);
};
```
See the [Forms](https://docs.gadget.dev/guides/frontend/forms) guide for comprehensive docs on building forms with `useActionForm`.
### Calling Actions directly
If you aren't building a form, or if you need lower-level control, the `useAction` hook can be used to call actions directly. `useAction` returns two values: a result object with the `data` from running, the `fetching` state, and the `error` if one was encountered, as well as an `act` function to run the backend action.
For example, with a `post` model on the backend, we can create a new post record by calling the `act` function returned by the `useAction` hook:
```tsx
import { useAction } from "@gadgetinc/react";
import { api } from "../api";
const [{ data, fetching, error }, act] = useAction(api.post.create);
// when ready, run the `act` function to trigger the action
act({ title: "Example Post", body: "some post content" });
```
The same approach is used for updating data with `useAction(api.post.update)`, but you must pass the `id` of the record you want to update:
```tsx
import { useAction } from "@gadgetinc/react";
import { api } from "../api";
const [{ data, fetching, error }, act] = useAction(api.post.update);
// when ready, run the `act` function to trigger the action
act({ id: "123", title: "Example Post", body: "some new post content" });
```
For more details on the `useAction` hook, see the [`@gadgetinc/react` reference](https://docs.gadget.dev/reference/react#useaction).
## Calling global actions
The [`@gadgetinc/react`](https://npmjs.com/package/@gadgetinc/react) React hooks library includes support for building forms for global actions with the [`useActionForm` hook](https://docs.gadget.dev/guides/frontend/forms), or calling global actions directly with the [`useGlobalAction` hook](https://docs.gadget.dev/reference/react/#useglobalaction).
`useGlobalAction` returns two values: a result object with the `data` from running, the `fetching` state, and the `error` if one was encountered, as well as an `act` function to run the backend global action.
For example, if you have a Global Action named `syncData`, we can run this action by calling the `act` function returned by the `useGlobalAction` hook:
```tsx
import { useGlobalAction } from "@gadgetinc/react";
import { api } from "../api";
const [{ data, fetching, error }, act] = useGlobalAction(api.syncData);
// when ready, run the `act` function to trigger the action
act({
// any params the global action might expect
});
```
We can use the `useGlobalAction` hook in a React component that calls the action when a button is clicked:
```tsx
import { useState } from "react";
import { useGlobalAction } from "@gadgetinc/react";
import { api } from "../api";
export const CreatePostForm = () => {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [{ data, fetching, error }, act] = useGlobalAction(api.syncData);
return (
<>
{error &&
Error: {error.message}
}
>
);
};
```
For more details on the `useGlobalAction` hook, see the [`@gadgetinc/react` reference](https://docs.gadget.dev/reference/react#useglobalaction).
## Calling HTTP routes
Backend [HTTP Routes](https://docs.gadget.dev/guides/http-routes) are available for calling from your frontend codebase. Calling these routes can be done with any HTTP client, but within the frontend, Gadget recommends using [the `useFetch` hook](https://docs.gadget.dev/reference/react#usefetch). `useFetch` provides a React wrapper around the built-in browser `fetch` function, and includes .
In a frontend React component, `useFetch` will make a request to a backend HTTP route. For example, if we have a `api/routes/GET-hello.js` file that sends a JSON reply in our backend like this:
```typescript
const route: RouteHandler = async ({ request, reply }) => {
reply.send({ message: "Hello from the backend!" });
};
export default route;
```
We can call this route in a frontend React component:
```tsx
import { useFetch } from "@gadgetinc/react";
const Component = () => {
const [{ data, fetching, error }, send] = useFetch("/hello", { json: true });
// will start out null, then when the data arrives, { message: "Hello from the backend!" }
console.log(data);
};
```
### Calling fetches imperatively
If you aren't using React, or would like to await a request like you might with the built-in browser `fetch` function, you can use the `api.fetch` function:
For example, we can call the `/hello` route from above like this:
```typescript
const result = await api.fetch("/hello").json();
console.log(result);
// { message: "Hello from the backend!" }
```
`api.fetch` is appropriate for use in an imperative context, like a server-side script, or other places where you don't need to give the user feedback about what's happening. `useFetch` is appropriate when you need to show the user feedback as the `fetching` or `error` state changes.
### Maintaining session state when calling HTTP routes
Different apps use different mechanisms to authenticate requests from the client to your Gadget backend. When making raw HTTP calls to your backend, you need to ensure that the correct authentication headers are passed to your backend. The `api` client object provided by Gadget sends these headers automatically for GraphQL requests and requests made by the React hooks.
The `useFetch` hook and `api.fetch` function implements this same automatic authentication header setting but for any HTTP request to the backend. `useFetch` and `api.fetch` wrap the browser built-in `fetch`, but add the headers required to implement whichever authentication mode is active for your app.
The different authentication modes are documented in your [API reference](https://docs.gadget.dev/api/example-app/development/authentication).
Here's an example user component that uses `useFetch` to make a request to a `api/routes/users/GET-me.js` backend Gadget route:
```tsx
export function UserByEmail() {
const [{ data, fetching, error }, refresh] = useFetch("/users/me", {
method: "GET",
json: true,
});
if (error) return <>Error: {error.toString()}>;
if (fetching) return <>Fetching...>;
if (!data) return <>No user found>;
return
{data.name}
;
}
```
#### Request method
By default, `GET` requests are sent as soon as the hook executes. `GET` requests can also be refreshed by calling the second return value to re-send the fetch request and fetch fresh data.
```tsx
// GET request will be sent immediately, can be refreshed by calling `refresh()` again
const [{ data, fetching, error }, refresh] = useFetch("/some/route", { method: "GET" });
// ... sometime later
// `data` will now be populated
data;
```
Other request methods like `POST`, `DELETE`, etc will not be sent automatically. The request will only be sent when the `send` functions is called explicitly, often in a click handler or similar.
```tsx
// POST requests are not sent immediately. They will only be sent when `send()` is called
const [{ data, fetching, error }, send] = useFetch("/some/route", { method: "POST" });
useEffect(() => {
async function callRoute() {
// make request to '/some/route' http route
await send();
}
void callRoute();
}, []);
// will be undefined until the useEffect is run
// and send() is called and request is completed
console.log(data);
```
#### Retrieving JSON
`useFetch` has a handy `json: true` option for automatically parsing a JSON response from the server. If you know your route will return JSON, you can set this option to `true` and the response will be parsed and returned as an object.
```tsx
import { useFetch } from "@gadgetinc/react";
export function User() {
const [{ data, fetching, error }, refresh] = useFetch("/users/me", {
method: "GET",
json: true,
});
if (error) return <>Error: {error.toString()}>;
if (fetching) return <>Fetching...>;
if (!data) return <>No user found>;
// no need to JSON.parse the result
return
{data.name}
;
}
```
The `json: true` option does not affect what is sent with your request, and only affects how the response is processed.
#### Sending JSON
To send a JSON formatted request with the `useFetch` hook, JSON.stringify your request body and set the `content-type: application/json` header:
```tsx
import { useFetch } from "@gadgetinc/react";
export function UpdateUser() {
const [{ data, fetching, error }, send] = useFetch("/users/update", {
method: "POST",
headers: {
"content-type": "application/json",
},
});
// sometime later in an event handler
return (
);
}
```
### Fetching with other React hooks
The `@gadgetinc/react` hooks library includes a `useFetch` hook, but if you'd like to use your preferred HTTP hook library, you can! By wrapping `api.fetch` with one of the great existing React libraries for making HTTP calls, like [use-http](https://github.com/ava/use-http), [swr](https://swr.vercel.app/) or [react-query](https://react-query-v3.tanstack.com/overview), you can continue passing the same authentication state and headers to your backend.
For example, we call a `/example` route in the backend with the `swr` library. First, create the route by adding the `api/routes/GET-example.js` file:
```typescript
import { RouteHandler } from "gadget-server";
const route: RouteHandler = async ({ reply }) => {
reply.send({ message: "Hello from the backend!" });
};
export default route;
```
Next, install `swr` into your application by adding the following to `package.json` and clicking the Run Yarn button:
```json
// in package.json
{
"swr": "^2.0.4"
}
```
Then, in your React component, import the `useSWR` hook from `swr` and the `api.fetch` function from your Gadget API client:
```tsx
import useSWR from "swr";
import { api } from "../api";
const fetcher = (args: RequestInfo | URL) => api.fetch(args).then((res) => res.json());
function Profile() {
const { data, error } = useSWR("/example", fetcher);
if (error) return
failed to load
;
if (!data) return
loading...
;
return
Backend message: {data.message}!
;
}
```
## Static asset handling
Gadget's frontend hosting supports serving static assets in a robust, CDN-friendly way using [Vite](https://vitejs.dev/guide/assets.html#static-asset-handling). You can add static assets anywhere within your `web` directory, and then import them into your code. For example, if you add an image at `web/images/hero.png`, you can import it in your frontend code like this:
```tsx
import imgUrl from "./images/hero.png";
export const Hero = () => {
return ;
};
```
When you \`import\` a static asset in your frontend code, Vite will produce an appropriate URL for that asset that works well in both development and production.
### Production asset links
When you deploy to production, Gadget runs your app's build process, and Vite builds production-safe URLs for all your static assets. These URLs include a cache hash, which means that each new version of your asset gets a unique URL. These unique URLs allow Gadget's CDN to cache the asset for maximum performance, and ensures users get new versions as soon as you deploy by changing the url.
If you change these static assets, Vite will generate a new hash and the updated asset will be loaded from Gadget's CDN on the next page render.
## Non-transformed public assets
Gadget's frontend can host assets that aren't transformed by Vite, like your app's `favicon` or a `robots.txt` file. Instead of being minified and cached by vite, these files will be served as-is from your app's filesystem.
You can store these assets in a `public` folder at the root level of your Gadget project.
Where possible, prefer using `import`\-ed assets in your code instead of storing them in the `public` folder. This way, Vite can automatically manage CDN cache expiry for you.
For example, if you have a public folder in your application like this:
```markdown
public/
favicon.ico
robots.txt
foo/
bar.txt
```
You can access your `robots.txt` file by making a request to:
* `https://example-app--development.gadget.app/robots.txt` in development
* `https://example-app.gadget.app/robots.txt` in production once deployed
The `public` folder also supports sub-folders. You can access the `public/foo/bar.txt` file by making a request to:
* `https://example-app--development.gadget.app/foo/bar.txt` in development
* `https://example-app.gadget.app/foo/bar.txt` in production once deployed
### Production cache expiry for non-transformed public assets
Gadget automatically caches all assets, both transformed assets from within `web` and non-transformed assets from `public` within public, on a high-performance CDN. Gadget also sets browser caching headers for these assets so browsers won't re-download them every page load, which improves user experience.
To maximize the cache hit rate for the best user experience, assets are cached by URL for long durations. For assets in the `public` folder, this means that when you change an asset, the new version will not be served to users until the cache expires.
If you change an asset, you can force a cache bust by changing the URL. For example, if you have a `robots.txt` file in your `public` folder, you can force a cache bust by changing the URL to `https://example-app--development.gadget.app/robots.txt?v=2`.
If you want to force a cache bust for all assets, you can add a query parameter to the URL. For example, you can add a query parameter to the URL to force a cache bust for all assets: `https://example-app--development.gadget.app/robots.txt?v=2`.
This cache busting process is automatically managed by Vite for assets you import in the web folder, so Gadget recommends using `import`\-ed assets in your code instead of storing them in the `public` folder where possible.
## Vite configuration
Gadget exposes the `vite.config.mjs` file that powers the Vite integration hosting your frontend. In `vite.config.mjs`, you can adjust the set of Vite plugins that power your application.
For example, we can add \[MDX\] support to a Gadget frontend with the `@mdx-js/rollup` plugin. First, install the plugin by adding the following to `package.json` and clicking the Run Yarn button:
```json
// in package.json
{
"@mdx-js/rollup": "^2.3.0"
}
```
Then, in our `vite.config.mjs`, we can add the plugin to the list of plugins:
```typescript
import react from "@vitejs/plugin-react";
import mdx from "@mdx-js/rollup";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
// newly added mdx plugin, configured as MDX recommends in their docs: https://mdxjs.com/docs/getting-started/#vite
{ enforce: "pre", ...mdx() },
// leave the existing react plugin in place
react(),
],
base: "/",
});
```
More Vite plugins can be found in [awesome-vite](https://github.com/vitejs/awesome-vite#plugins).
## Strict mode
By default, your React frontends will be in [strict mode](https://react.dev/reference/react/StrictMode).
This is set in the `web/main.jsx` file with ``, or by the framework defaults in the case of Remix and React Router.
Strict mode helps you catch bugs in development by [automatically re-rendering your app](https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development) and [re-running your `useEffect` hooks](https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-re-running-effects-in-development). This only occurs in development and will not impact production apps.