How to fetch data on client side inside the admin panel?

Shopify Partner
1 0 0

I have a surprising problem: something that I thought would be super easy, but I already spent three hours on that and still hadn't figured it out.


What I'm trying to do:

Basically, I have a simple Index Table that displays 20 products and a "Load More Products" button below. I load initial products through a simple loader... but I have absolutely no idea how to load additional products without refreshing the page.


Here's my code to illustrate what I'm trying to do (I cut out irrelevant parts):


export const loader = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const query = '' // Query loading products, I cut it out for brievity, but it works.

const response = await admin.graphql(query);
const response_json = await response.json();

return json({ products: });

export default function Index() {
const { products } = useLoaderData();
const [currentProducts, setCurrentProducts] = useState(products.edges)
const [hasNextPage, sethasNextPage] = useState(products.pageInfo.hasNextPage);

const loadMoreProducts = async () => {
const cursor = currentProducts[currentProducts.length - 1].cursor;
const query = `
{products(first: 20, after: ${JSON.stringify(cursor)})` // Rest of the query would be the same as the previous one.

// Here I should actually run this query... but I have no idea how to do it.


Where I'm stuck:

- I don't really know a different way to query GraphQL than by using admin.graphql(query).

- However, I don't know how to authenticate admin outside of the loader. So I don't have access to admin.qraphql outside of the loader.


(I'm new to React and GraphQL, so I'm not going to be surprised if the problem is simply caused by me misunderstanding something fundamental. If that's true, I'll deeply appreciate letting me know what I should do instead!)


Reply 1 (1)

Shopify Partner
19 1 4

Yeah this is kind of an existential problem for any React app!


The real issue is that the admin token used needs to be guarded, since it is a secret. If it's used client side in a request, then anyone sniffing the network in a coffee shop where the merchant is accessing the app could take it, and do whatever they wanted in a merchant's store.

With Remix, you have flexibility to create your own routes, so I created some utility routes that validate the request with `authenticate.admin(request)`, and then return data. That way, if I `fetch('/app/api/endpoint')` from my client, I get data back, but if I ping `http://localhost:3000/app/api/endpoint` from the outside, I get a 401.