How to Pause Queries on Inactive Browser Tabs Using Graph-Client

Introduction

In this blog post we will discuss one of the most effective ways in which you can optimize  the amount of queries to The Graph Network from within your dapp – effectively reducing instances in which unneeded queries are made. Following this guide will also optimize your applications performance by implementing a real time data fetching pattern.

One important factor you should consider when querying a subgraph is how often the data changes. As an example, the Ethereum network creates a new block about every 12 seconds. Therefore if your application is fetching data every second, you can assume that in most cases 11 out of those 12 requests are wasted. Additionally, if you want a good user experience for your application, you want the data to update automatically and if they tab away from your page and come back, you don’t want them to have to refresh the page, but rather automatically start pulling the data they need in the UI.

The good news is that both of these key objectives can be achieved by enabling real-time data queries (live queries) and limiting data streaming to only active browser tabs, both of which are powerful features of Graph Client.

The below steps will walk through an example implementation, which can be found in this GitHub repo.

Setup

In this example we will use NextJS. If you need instructions on how to set up a NextJS project read the following documentation to get started quickly.

Once you have a project setup, you will need to install `@graphprotocol/client-cli`, `@graphprotocol/client-polling-live` and enable live queries. To install graph-cli and client-polling-live run the following with the package manager of your choice.

yarn:

yarn add -D @graphprotocol/client-cli @graphprotocol/client-polling-live

npm:

npm install --save-dev @graphprotocol/client-cli @graphprotocol/client-polling-live

Configuration

To enable live queries, add the following to your `.graphclientrc.yml` configuration file. You can set the defaultInterval to whatever you prefer, the default value for `defaultInterval` is `1000`. The interval can also be configured on individual queries if it’s preferred. Since we will be querying an Ethereum subgraph, setting the interval to 11000 milliseconds (11 seconds) will align with the frequency of new blocks.

plugins:
- pollingLive:
defaultInterval: 11000

The Substreams Uniswap v3 Ethereum is a good example of an active subgraph that gets new data frequently. To configure Graph Client to use this subgraph, create a `.graphclientrc.yml` and define the `sources.name` as mainnet along with the `handler.graphql.endpoint`. You will need to replace the `<APIKEY>` in the url with your own API key. You can generate an API key on The Graph website under the API Keys section of Subgraph Studio.

# .graphclientrc.yml
sources:
- name: mainnet
handler:
graphql:
endpoint: https://gateway-arbitrum.network.thegraph.com/api/<APIKEY>/subgraphs/id/HUZDsRpEVP2AvzDCyzDHtdc64dyDxx8FQjzsmqSg4H3B

A best practice is to store your GraphQL queries in a dedicated directory. To do this, add the documents configuration to the `.graphclientrc.yml` configuration. In the below example all files with the extension `.graphql` will be loaded from the `queries` folder.

documents:
- "./queries/*.graphql"

In this example we have a query file named `MyQuery.graphql`. In that query the `@live` parameter will tell Graph Client to query for real-time data.

query MyQuery @live {
transactions(first: 1, orderBy: timestamp, orderDirection: desc) {
swaps(first: 1) {
amountUSD
amount0
amount1
pool {
token1 {
name
}
token0 {
name
}
}
}
}
}

After you’ve created the query, you will need to generate the unified schema, artifacts and `index.tsx` file. To do this run `yarn graphclient build`

yarn run v1.22.21
$ /Users/crashoverride/my-app/node_modules/.bin/graphclient build
💡 GraphClient Cleaning existing artifacts
💡 GraphClient Reading the configuration
💡 GraphClient Generating the unified schema
💡 GraphClient Generating artifacts
💡 GraphClient Generating index file in TypeScript
💡 GraphClient Writing index.ts for ESM to the disk.
💡 GraphClient Cleanup
💡 GraphClient Done! => /Users/kevinjones/my-app/.graphclient
✨ Done in 2.87s.

If you would like to test your Graph Client configuration. You can load the Yoga GraphiQL server by running `yarn graphclient serve-dev`. This will launch the GraphiQL interface in a new browser window. Add the query from above and click the Execute Query button.

As you can see in the examples a live query response contains an additional boolean key named `isLive` and the data will refresh automatically based on the configuration. Below you can see the full payload response.

{
"data": {
"transactions": [
{
"swaps": [
{
"amount0": "-536.190827804100513073",
"amount1": "0.402278485649731011",
"amountUSD": "1288.501022062263933522516181107626",
"pool": {
"token0": {
"name": "Safe Token"
},
"token1": {
"name": "Wrapped Ether"
}
}
}
]
}
]
},
"isLive": true
}

Next, add set `pauseOnBackground` to true so that only active browser sessions will perform live queries. The default value for this directive is `true`, however it is recommended to explicitly define it for anyone reviewing the configuration will know the behavior of your application.

plugins:
- pollingLive:
defaultInterval: 10000
pauseOnBackground: true

Frontend Configuration

After the configuration of Graph Client is complete, the next step is to configure the frontend application to subscribe to the live queries. A full example of this code can be found in the project’s main page under `app/page.tsx`. Let’s walk through the frontend configuration. First, import all the types and hooks needed to render the page properly.

import { useEffect, useState } from "react";
import type { NextPage } from "next";
import { MyQueryDocument,
MyQueryQuery,
subscribe
} from "../.graphclient";
import { ExecutionResult } from "graphql";

The most notable configuration changes here are the imports of the types that were generated earlier using Graph Client CLI along with the type definitions from the GraphQL library.

Next, within the Next application you will fetch the data, since this is a live query the configuration should use the `subscribe()` function to fetch the data we need in our application.

const [result, setResult] = useState<ExecutionResult<MyQueryQuery> | null>(null);
useEffect(() => {
let shouldContinue = true;
const fetchData = async () => {
try {
const fetchedResult = await subscribe(MyQueryDocument, {});
if ("data" in fetchedResult) {
if (shouldContinue) {
setResult(fetchedResult);
}
} else {
for await (const result of fetchedResult) {
if (!shouldContinue) break;
setResult(result);
}
}
} catch (error) {
console.error("Failed to fetch data:", error);
}
};
fetchData();
return () => {
shouldContinue = false;
};
}, []);

Lastly, the data can be rendered in the return statement. In the below example we use `.map` to build a dynamic list of items. This is here as an example implementation but would need to be custom based on your query results.

return (
<>
<div className="flex items-center flex-col flex-grow pt-10">
<div className="px-5">
<h1 className="text-center">
<span className="block text-4xl font-bold">Query The Graph</span>
</h1>
</div>
<div className="flex-grow bg-base-300 w-full mt-16 px-8 py-12">
<ul>
{result?.data?.transactions.map((transaction, txnIndex) =>
transaction.swaps.map((swap, swapIndex) => (
<li key={`${txnIndex}-${swapIndex}`}>
<div>Amount0: {swap?.amount0 ?? "N/A"}</div>
<div>Amount1: {swap?.amount1 ?? "N/A"}</div>
<div>USD Amount: {swap?.amountUSD ?? "N/A"}</div>
<div>Pool Token0: {swap?.pool.token0?.name ?? "Unknown"}</div>
<div>Pool Token1: {swap?.pool.token1?.name ?? "Unknown"}</div>
</li>
)),
)}
</ul>
</div>
</div>
</>
);

Documentation Links

  • GitHub docs for Live Queries (https://github.com/graphprotocol/graph-client/blob/main/docs/live.md)
  • Live Queries [The Guild] (https://the-guild.dev/graphql/mesh/docs/plugins/live-queries)

Conclusion

In conclusion, optimizing queries to The Graph by detecting inactive browser tabs significantly enhances the efficiency and performance of dapps. By implementing real-time data fetching and limiting active queries to only when the browser tab is in use, developers can reduce unnecessary requests and ensure that users receive timely and relevant data updates without the need for manual refreshes. This approach not only conserves resources but also provides a smoother and more responsive user experience.

About The Graph

The Graph is the source of data and information for the decentralized internet. As the original decentralized data marketplace that introduced and standardized subgraphs, The Graph has become web3’s method of indexing and accessing blockchain data. Since its launch in 2018, tens of thousands of developers have built subgraphs for dapps across 80+ blockchains - including  Ethereum, Solana, Arbitrum, Optimism, Base, Polygon, Celo, Fantom, Gnosis, and Avalanche.

As demand for data in web3 continues to grow, The Graph enters a New Era with a more expansive vision including new data services and query languages, ensuring the decentralized protocol can serve any use case - now and into the future.

Discover more about how The Graph is shaping the future of decentralized physical infrastructure networks (DePIN) and stay connected with the community. Follow The Graph on X, LinkedIn, Instagram, Facebook, Reddit, Farcaster and Medium. Join the community on The Graph’s Telegram, join technical discussions on The Graph’s Discord.

The Graph Foundation oversees The Graph Network. The Graph Foundation is overseen by the Technical Council. Edge & Node, StreamingFast, Semiotic Labs, Messari, GraphOps, Pinax and Geo are eight of the many organizations within The Graph ecosystem.


Categories
Developer CornerRecommended
Published
August 1, 2024

Kevin Jones

View all blog posts