Building single-click app frontends
BigCommerce apps built with Gadget have React frontends that have the BigDesign library pre-installed. These frontends are:
- powered by Vite
- built with BigDesign
- serverless
- hosted and scaled by Gadget
- come with OAuth and API client set up out of the box
Your frontend files are in the web/
directory of your Gadget app.
Gadget has authorization built-in, so frontend app users will not be able to call actions until they are granted permission. You can manage API and data permissions in accessControl/permissions
. Read more on frontend data security and access control below.
For more general information on building frontends with Gadget, see the frontend guide.
Getting started with single-click frontends
After setting up a BigCommerce connection, your web/
directory will contain the following files:
File path | Description |
---|---|
web/api.js | An API client unique to your Gadget project and used to call your backend. |
web/main.jsx | The entry point for your frontend project, sets up the BigDesign ThemeProvider . |
web/components/App.jsx | Set up routing and authenticates API client using Provider from @gadgetinc/react-bigcommerce . |
web/routes/index.jsx | The default route that renders the frontend you see in a store. |
You can start to make edits right away in web/routes/index.jsx
and hot module reloading (HMR) will update your frontend in realtime.
Reading data from your backend
You can use the API client in web/api.js
along with React hooks from the @gadgetinc/react
package to call your backend actions or HTTP routes, which reads data from your database.
If you need data that is not being stored in your database, you can make requests to the BigCommerce API from an action. We do not recommend making requests to the BigCommerce API directly from your frontend.
Using @gadgetinc/react
hooks to read data
The @gadgetinc/react
package provides hooks to call your backend actions or HTTP routes. You can use these hooks to read data from your database. These hooks provide a simple, React-ful way to interact with your API and provide additional functionality like:
- loading/fetching state,
- error handling,
- pagination, filtering, sorting, and searching,
- and realtime queries
As an example, this is how you could read a list of products from your database using a useFindMany
hook. This will fetch the first 50 products from the database and display them in a table, with a loading spinner while the request is fetching data. An error will also be displayed if there is an issue reading data:
web/routes/index.jsxReact1import { Panel, Text, Flex, StatefulTable, ProgressCircle } from "@bigcommerce/big-design";2import { ErrorIcon } from "@bigcommerce/big-design-icons";3import { useFindMany } from "@gadgetinc/react";4import { api } from "../api";56// simple currency formatter7let CAD = new Intl.NumberFormat("en-CA", {8 style: "currency",9 currency: "CAD",10});1112export default function () {13 // fetch the first 50 products from the database14 const [{ data, fetching, error }] = useFindMany(api.bigcommerce.product);1516 // add an error message to the Panel if there is a request error17 return (18 <Panel19 title="Read data example"20 description={21 error ? (22 <Flex flexGap={"8px"}>23 <ErrorIcon color="danger" /> <Text>Error: {error.message}</Text>24 </Flex>25 ) : undefined26 }27 >28 {/** render a loading spinner while the request is fetching data */}29 {fetching && <ProgressCircle size="large" />}30 {/** draw a paginated product table with the returned data */}31 {data?.length > 0 ? (32 <StatefulTable33 pagination34 columns={[35 { header: "Name", hash: "name", render: ({ name }) => name },36 { header: "Price", hash: "price", render: ({ price }) => CAD.format(price) },37 ]}38 items={data}39 />40 ) : (41 <Text>No product data in database!</Text>42 )}43 </Panel>44 );45}
Docs on pagination, filtering, sorting, and searching on read can be found in your your API reference.
Your Gadget API supports realtime queries out of the box. Using realtime queries will keep your frontend up to date with your database without needing to poll your backend.
Reading data from BigCommerce
If you want to read data that is not stored in your Gadget database, you can make requests to the BigCommerce API from your backend action. This is the recommended way to read data from BigCommerce in your frontend.
Here is an example of how you could read product data from BigCommerce in a global action, call that action from your frontend in a useEffect
hook, and display the returned data in a table.
First, the global action:
api/actions/bigcommerce/readProducts.jsJavaScript1import { BigCommerceReadProductsGlobalActionContext } from "gadget-server";23/**4 * @param { BigCommerceReadProductsGlobalActionContext } context5 */6export async function run({ params, logger, api, connections }) {7 // BigCommerce API client8 const bigcommerce = connections.bigcommerce.current;9 // use the API client to fetch 5 products, and return10 return await bigcommerce.v3.get(`/catalog/products?limit=5`);11}
And here is how you can call that global action from your frontend using the useGlobalAction
hook. This will fetch the first 5 products from BigCommerce and display them in a table, with a loading spinner while the request is fetching data. An error will also be displayed if there is an issue calling the action:
web/routes/index.jsxReact1import { Panel, Text, Flex, StatefulTable, ProgressCircle } from "@bigcommerce/big-design";2import { ErrorIcon } from "@bigcommerce/big-design-icons";3import { useGlobalAction } from "@gadgetinc/react";4import { api } from "../api";5import { useEffect } from "react";67// simple currency formatter8let CAD = new Intl.NumberFormat("en-CA", {9 style: "currency",10 currency: "CAD",11});1213export default function () {14 // use the useGlobalAction hook to call a global action15 const [{ data, fetching, error }, readProducts] = useGlobalAction(api.bigcommerce.readProducts);1617 useEffect(() => {18 // call the function provided by the hook that runs the action19 readProducts();20 }, []);2122 // add an error message to the Panel if there is a request error23 return (24 <Panel25 title="Read data directly from BigCommerce example"26 description={27 error ? (28 <Flex flexGap={"8px"}>29 <ErrorIcon color="danger" /> <Text>Error: {error.message}</Text>30 </Flex>31 ) : undefined32 }33 >34 {/** render a loading spinner while the request is fetching data */}35 {fetching && <ProgressCircle size="large" />}36 {/** draw a paginated product table with the returned data */}37 {data?.length > 0 ? (38 <StatefulTable39 pagination40 columns={[41 { header: "Name", hash: "name", render: ({ name }) => name },42 { header: "Price", hash: "price", render: ({ price }) => CAD.format(price) },43 ]}44 items={data}45 />46 ) : (47 <Text>No product data found!</Text>48 )}49 </Panel>50 );51}
You will also have to grant the bigcommerce-app-users
role access to the bigcommerce/readProducts
action in
accessControl/permissions
!
Writing data to your database
To write data to your database, you can make use of the useAction
, useGlobalAction
, or useActionForm
hooks from the @gadgetinc/react
package. These hooks allow you to call your backend actions from your frontend.
Simple example with useAction
For example, if you wanted to save a record to a gizmo
model when a button is clicked, you could use the useAction
hook to call the gizmo.create
action:
simple useAction hook to call a model actionReact1import { Panel, Button, Box } from "@bigcommerce/big-design";2import { useAction } from "@gadgetinc/react";3import { api } from "../api";45export default function () {6 // use the useAction hook to call a global action7 const [{ fetching: savingGizmo }, createGizmo] = useAction(api.gizmo.create);89 // add an error message to the Panel if there is a request error10 return (11 <Panel title="Write data example">12 <Box marginTop="xxLarge">13 <Button onClick={async () => await createGizmo()} disabled={savingGizmo}>14 Add keyword15 </Button>16 </Box>17 </Panel>18 );19}
Writing data back to BigCommerce
If you want to write data back to BigCommerce, you can use the BigCommerce API client in your backend action. This is the recommended way to write data back to BigCommerce in your frontend.
Here is an example of how you could write product data to BigCommerce in a global action, and then call that action from your frontend using useGlobalAction
. First, the action:
api/actions/createMetafield.jsJavaScript1import { BigCommerceCreateMetafieldGlobalActionContext } from "gadget-server";23/**4 * @param { BigCommerceCreateMetafieldGlobalActionContext } context5 */6export async function run({ params, logger, api, connections }) {7 // set up BigCommerce client8 const bigcommerce = connections.bigcommerce.current;9 // create a metafield using custom data from the frontend10 await bigcommerce.v3.post("/catalog/products/metafields", {11 body: [12 {13 permission_set: "app_only",14 namespace: "metafield-namespace",15 key: "metafield-key",16 value: params.value,17 description: "Mu metafield description",18 resource_id: params.productId,19 },20 ],21 });22}2324// define custom params for the action25export const params = {26 productId: { type: "string" },27 value: { type: "string" },28};
And here is how you can call that global action, passing in values for the custom productId
and value
params, from your frontend using the useGlobalAction
hook:
web/routes/index.jsxReact1import { Panel, Form, FormGroup, Input, Button, Box } from "@bigcommerce/big-design";2import { useGlobalAction } from "@gadgetinc/react";3import { api } from "../api";4import { useState } from "react";56export default function () {7 const [value, setValue] = useState("");89 // use the useGlobalAction hook to call the createMetafield global action10 const [{ fetching }, createMetafield] = useGlobalAction(api.bigcommerce.createMetafield);1112 return (13 <Panel title="Read data example">14 <Form15 onSubmit={async (event) => {16 event.preventDefault();17 await createMetafield({ productId: 1, value });18 }}19 >20 <FormGroup>21 <Input22 description="Please enter a metafield value."23 label="Metafield value"24 required25 value={value}26 onChange={(event) => setValue(event.target.value)}27 disabled={fetching}28 />29 </FormGroup>30 <Box marginTop="xxLarge">31 <Button type="submit" disabled={fetching}>32 Create metafield33 </Button>34 </Box>35 </Form>36 </Panel>37 );38}
This pattern allows you to send data to BigCommerce on a frontend action. Firing an action doesn't need to use a form! You can use any event to trigger an action, like a button click.
Building forms
If you are building a form, you can use the useActionForm
hook from the @gadgetinc/react
package. This hook simplifies form handling by providing a way to handle form state, validation, and submission.
The useActionForm
hook wraps the react-hook-form
library and works nicely with BigDesign. Here is an example of how you could use useActionForm
to create a form that writes data to a manufacturer
model:
web/routes/index.jsxReact1import { Panel, Form, FormGroup, Input, Button, Box } from "@bigcommerce/big-design";2import { useActionForm } from "@gadgetinc/react";3import { api } from "../api";45export default function () {6 // useActionForm calls the manufacturer.create action on submit7 const { submit, register } = useActionForm(api.bigcommerce.manufacturer.create);89 // add an error message to the Panel if there is a request error10 return (11 <Panel title="Simple form example">12 <Form onSubmit={submit}>13 <FormGroup>14 {/** register is used to associate the Input component with the "name" field on the model */}15 <Input description="Enter the manufacturer name." label="Manufacturer name" required {...register("name")} />16 </FormGroup>17 <Box marginTop="xxLarge">18 <Button type="submit">Create manufacturer</Button>19 </Box>20 </Form>21 </Panel>22 );23}
Notice how there are no useState
hooks managing form data. The useActionForm
hook handles all of that for you!
For more information on building forms and additional examples, see the forms guide.
The BigDesign component library extends the native HTML form elements with additional props. This is great news when working with
useActionForm
, as all of the BigDesign components are compatible with the register
function provided by useActionForm
.
The register function is used to associate the BigDesign component with the field on the model, handles form state, and allows you to add custom frontend validation.
Calling HTTP routes
There are cases where you need to use an HTTP route instead of an action, for example, when you need to stream a response back to your frontend.
The @gadgetinc/react
package has a useFetch
hook for calling HTTP routes, or you can use api.fetch()
built into the Gadget API client.
More information on calling HTTP routes from your frontend can be found in the calling HTTP routes guide.
Calling the BigCommerce API from an HTTP route
When you make a request to a BigCommerce API from an HTTP route, you will need to make sure the instance of the Gadget API client in your route is properly authenticated. You can use connections.bigcommerce
to create an instance of the BigCommerce API client, similar to actions.
Authenticated API clients
If you use the useFetch()
hook from within a single-click frontend, your API client will already be authenticated with the BigCommerce store. This means you can use connections.bigcommerce.current
to create an instance of the BigCommerce API client in your HTTP route.
api/routes/GET-products.jsJavaScript1export default async function route({ request, reply, connections }) {2 const bigcommerce = connections.bigcommerce.current;3 const products = await bigcommerce.v3.get(`/catalog/products?limit=5`);4 await reply.send({5 products,6 });7}
Unauthenticated API clients
If you are calling your HTTP route from an external source, you will need to authenticate the API client with the store hash. You can use connections.bigcommerce.forStoreHash()
to create an instance of the BigCommerce API client in your HTTP route.
api/routes/GET-products.jsJavaScript1export default async function route({ request, reply, connections }) {2 const bigcommerce = connections.bigcommerce.forStoreHash("<add-your-store-hash>");3 const products = await bigcommerce.v3.get(`/catalog/products?limit=5`);4 await reply.send({5 products,6 });7}
Frontend data security and access control
Gadget also handles authorization and access control for your APIs. When you make a request to your backend, Gadget will check if the user has the correct permissions to access the data. If the user does not have the correct permissions, Gadget will return a 403 error.
You can see permissions for your app at accessControl/permissions
.
BigCommerce merchants who interact with your app frontend are granted a bigcommerce-app-users
role. By default, they will only have read and write access to the bigcommerce/store/
model's actions.
For any additional model or global actions, you will need to manually grant the bigcommerce-app-users
role access to those actions.
HTTP routes are not secure by default, so it is recommended to use actions unless you have a specific need for a route.
It is a good idea to only grant the permissions that are necessary for your app to function. This will help keep your app secure and prevent unauthorized access to your data.
If you are building a public single-click app, you need to handle multi-tenancy so stores cannot access each other's data. Read our multi-tenancy docs for more information.
session
management
The BigCommerce connection manages records of the backend session
model when using @gadgetinc/react-bigcommerce
. When a merchant loads a single-click frontend, the <Provider />
will:
- handle the call to the
/load
endpoint - verify the
jwt
payload - ensure the user matches the store owner (when multiple users are not enabled), or manage the user session when multiple users are enabled
If the payload is valid, the connection will provision a new session
record for the userId
and store
. Valid sessions will be granted the bigcommerce-app-users
role, which is used to manage access to actions and models. This session is then passed to all your backend application's model filters and is available within actions.