Using dynamic queries with useDataQuery
This tutorial is a continuation of the DHIS2 application runtime tutorial on Fetching data with useDataQuery.
In this tutorial, you will do the following:
- Understand the difference between static and dynamic queries
- Learn how to define query variables for dynamic queries
- Fetch data using the
useDataQueryhook with dynamic parameters
Getting started
1. Introduction to dynamic queries
In the previous tutorial, we learned how to fetch data using a static query with useDataQuery. Static queries have all parameters fixed in the query definition. In real applications, however, queries often need to change based on user input or navigation - for example, retrieving a different page of results or filtering data by a search term. This is where dynamic queries come in.
Dynamic queries allow parts of the query to be determined at runtime using variables. The DHIS2 application runtime supports dynamic queries by letting us declare query parameters (or even resource IDs) as functions that depend on variables. We can then supply those variables when calling the useDataQuery hook (or via the refetch function) to fetch data on demand. This enables common use cases like pagination, user-driven filtering, or toggling how much data to load, all using the same useDataQuery hook.
Note: To follow this guide, you should have a React application set up with
@dhis2/app-runtime(See the Getting Started section in the Previous Tutorial). We will build on the previous example that fetched a list of programs.
2. Define a dynamic data query
Let's extend our previous data query example to support dynamic variables. In the static example, we fetched the first 5 programs using a fixed pageSize of 5. Now, we want to allow fetching other pages of the programs list (e.g., page 2, page 3, etc.). To do this, we will introduce a dynamic page parameter in the query definition.
In the query object below, the params property is defined as a function that takes an object with our variables. We use destructuring to extract the page variable and return an object with the query parameters. This allows the page value to be provided later when we execute the query.
// ...
const PAGE_SIZE = 5
const myQuery = {
results: {
resource: 'programs',
params: ({ page = 1 }) => ({
pageSize: PAGE_SIZE,
page, // fetches the given page of results
fields: ['id', 'displayName'],
}),
},
}
// ...
In the above code, params is set to a function. The DHIS2 app runtime will call this function with an object containing any variables we pass in. The function should return an object of parameters. Here we return a simple object with pageSize, page and fields. We also set a default value for page (page = 1) so that our query behaves predictably even before we introduce query variables in the next step. We still request 5 items per page (PAGE_SIZE) and the same fields (id and displayName) as before.
Note: You can make other parts of a query dynamic in a similar way. In the official docs, dynamic queries are typically implemented by making
paramsand/oriddepend on variables, whileresourceis expected to be a static string in the query definition.If you truly need to query different endpoints, it’s usually clearer to define multiple query objects (each with a different
resource) and choose between them in your code.Ensure that any parameter names in your query (like
pageorfilter) match the DHIS2 Web API's parameter names.
Clarification: resource vs resource ID
A common source of confusion is mixing up the resource (the Web API endpoint) with the resource ID (which identifies a specific item under that endpoint). In the app runtime, these are typically separate.
Below is an example taken from the datastore-app:
export const dataStoreKeysQuery = {
results: {
resource: 'dataStore',
id: ({ id }) => id,
},
}
Here, resource stays constant while id is provided dynamically via query variables.
3. Fetching data with dynamic variables
Now that we have a dynamic query defined, let's see how to use it with the useDataQuery hook. The process is similar to the static query, but we will also utilize the refetch function provided by useDataQuery to update our query variables at runtime.
Import useDataQuery
As before, we need to import the useDataQuery hook in addition to React if it is not already imported.
To follow best practices, the additional imports below are also commonly included in a real DHIS2 app:
@dhis2/uicomponents (pagination and loading states)@dhis2/d2-i18nfor translations
import React from 'react'
import { useDataQuery } from '@dhis2/app-runtime'
import { Pagination, CircularLoader } from '@dhis2/ui'
import i18n from '@dhis2/d2-i18n'
// ...
Use the dynamic query in a component
Next, we'll create a React component (e.g., MyApp) that uses useDataQuery with our dynamic query.
In this example, we'll fetch page 1 on load by passing initial variables through the options argument. We'll also use DHIS2 UI components for pagination and loading state to keep the code focused on data fetching logic.
Notice that we destructure an additional value refetch from the hook. The refetch function allows us to manually re-run the query with new variables. We'll use this to load the next page of data on a button click.
// ...
const PAGE_SIZE = 5
const myQuery = {
results: {
resource: 'programs',
params: ({ page = 1 }) => ({
pageSize: PAGE_SIZE,
page,
fields: ['id', 'displayName'],
}),
},
}
const MyApp = () => {
const { error, loading, data, refetch } = useDataQuery(myQuery, {
variables: { page: 1 },
})
const pager = data?.results?.pager
const handlePageChange = (nextPage) => {
refetch({ page: nextPage })
}
if (error) {
return <span>{`${i18n.t('ERROR')}: ${error.message}`}</span>
}
// Show a full-page loader on the initial load
if (loading && !data) {
return <CircularLoader />
}
return (
<div className={classes.container}>
<h1>{i18n.t('Programs')}</h1>
{/* Show a smaller loader while refetching */}
{loading && <CircularLoader />}
{data && (
// We use a simple list for readability in this guide.
// In a real app, you might prefer a DHIS2 UI DataTable (or another semantically appropriate component).
<ul>
{data.results?.programs?.map((prog) => (
<li key={prog.id}>{prog.displayName}</li>
))}
</ul>
)}
{pager && (
<Pagination
page={pager.page}
pageCount={pager.pageCount}
pageSize={PAGE_SIZE}
total={pager.total}
onPageChange={handlePageChange}
hidePageSizeSelect={true}
/>
)}
</div>
)
}
export default MyApp
In the code above, we call useDataQuery(myQuery, ... ) to fetch data. This ensures we start on page 1 and makes it explicit which variables the query expects.
The Pagination component is wired to the handlePageChange callback. When the user changes page, we call refetch({ page: nextPage }). This tells the runtime to re-run myQuery with the new page variable. The useDataQuery hook will fetch the results for that page and update the data object in our component.
Tip: When possible, use DHIS2 UI components (like
PaginationandCircularLoader) instead of building pagination controls and loading indicators from scratch. This helps you avoid reinventing the wheel and lets you focus on the runtime query logic.
Check your browser
With the dynamic query in place, run your application and open it in the browser. You should see the list of programs for page 1. When you change page using the pagination controls, the app will fetch and display the next set of programs from the DHIS2 instance:

You have now fetched data using a dynamic query. Each time the user changes page (via the pagination controls), we update the query variables and retrieve new data from the API without any manual HTTP calls.
You can try extending this example by experimenting with dynamic filters - for instance, allowing a user to input a search term and refetching with a filter parameter.
Want to learn more?
-
Check the DHIS2 application runtime documentation or watch this short video presentation introducing the app runtime (about 30 min)
-
Get an overview of the DHIS2 Web API in this video presentation (about 30 min) or check out the DHIS2 Core Web API documentation
-
The Web Academy 2025 App Runtime section includes hands-on tasks on data queries and pagination
-
Another example of using dynamic queries to fetch pages can be found here
What's Next?
In the next section, you’ll learn how to use the useDataMutation hook to mutate or write data!