Using a Bryntum TreeGrid with the GitHub GraphQL API: Lazy loading, on-demand data fetching, and remote filtering
When working with large data sets in a grid, lazy loading and on-demand data fetching are two common strategies to improve performance. The Bryntum Grid lazy data loading feature, also known as infinite scrolling, lets you load data in chunks as a user scrolls down the grid.
If you have data with a tree structure, where there are parent-child relationships, you can use the Bryntum TreeGrid to load child data on demand when a parent node is expanded.
In this tutorial, we’ll show you how to create a React Bryntum TreeGrid, load child data on demand, and use the lazy-load feature. We’ll use the GitHub GraphQL API to get a large dataset: the issues and issue comments in the VS Code GitHub repository:
This is the second article in a two-part series covering lazy loading. Part 1, Infinite scrolling with the new Bryntum lazyLoad feature, also shows you how to implement infinite scrolling using the Bryntum Grid’s lazy-loading feature. It covers important considerations when using lazyLoad, configuration, the expected server response, remote filtering, and remote sorting. A vanilla JavaScript Bryntum Grid is used with an Express server that serves a large dataset (100,000 records) from a JSON file.
Getting started
Clone the Bryntum TreeGrid app starter repository. The completed-grid
branch contains the code for the completed tutorial.
The starter repository is a Next.js app with a Grid
component, where we’ll create the Bryntum TreeGrid. The Grid
component is dynamically imported in the GridWrapper
component with server-side rendering turned off. We need to do this because Bryntum components are client-side only. The GridWrapper
component is rendered in the Home
page.
Install the dependencies using the following command:
npm install
Before we make a Bryntum TreeGrid, let’s learn how to use the GitHub GraphQL API, which will be our data source.
Using the GitHub GraphQL API
The GitHub GraphQL API lets you query GitHub repository data. The API requires authentication, which you can do using a GitHub personal access token. To create one that can read public repository data, generate a fine-grained personal access token in your GitHub Settings menu and set the Repository access to “Public Repositories (read-only)”.
Using the GitHub GraphQL API explorer and pagination
The GitHub GraphQL API Explorer lets you test queries. Add the following query to the explorer to get issues from a GitHub repo:
query ($first: Int!, $owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
issues(first: $first) {
totalCount
edges {
node {
id
author {
login
}
number
title
comments {
totalCount
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
This query uses three variables:
$first
: Specifies the number of issues to retrieve.$owner
: The GitHub account that owns the repository.$name
: The name of the repository.
We query the edges
, which represent the connections to individual issue nodes. For each node
, we fetch details including the id
, author
, title
, and the totalCount
of comments.
The pageInfo
object provides pagination details like the endCursor
and hasNextPage
values. Each returned edge has a cursor that’s used as a marker for pagination. The endCursor
is the cursor for the last issue returned.
Add the following variables below the query to get the first 100 issues in the VS Code GitHub repository:
{
"first": 100,
"owner": "microsoft",
"name": "vscode"
}
Run the query by clicking the pink Execute query button. You’ll see the returned data in the panel to the right of the query:
You can get the next 100 issues by adding an after
argument to the issues
field in the query and setting its value to the returned endCursor
string.
Adding your GitHub personal access token to the starter repository
In your cloned Bryntum TreeGrid app starter repository, create a .env
file in the root directory and add your GitHub personal access token to it:
GITHUB_PERSONAL_ACCESS_TOKEN=<your-access-token>
Creating and configuring a basic Bryntum TreeGrid
First, install the Bryntum Grid component by following the instructions to access the Bryntum npm registry, and then install the Grid and React Grid wrapper.
Next, replace the code in the src/components/Grid.tsx
file with the following lines of code to create a basic Bryntum TreeGrid:
import { useRef } from 'react';
import { BryntumTreeGrid, BryntumTreeGridProps } from '@bryntum/grid-react';
export default function Grid() {
const gridRef = useRef<BryntumTreeGrid>(null);
const gridProps: BryntumTreeGridProps = {
animateTreeNodeToggle : true,
data : [
{
id : 1,
name : 'ABBA',
iconCls : 'b-icon b-fa-users',
born : null,
expanded : true,
children : [
{ id : 11, name : 'Anni-Frid', born : 1945, iconCls : 'b-icon b-fa-user' },
{ id : 12, name : 'Bjorn', born : 1945, iconCls : 'b-icon b-fa-user' },
{ id : 13, name : 'Benny', born : 1946, iconCls : 'b-icon b-fa-user' },
{ id : 14, name : 'Agnetha', born : 1950, iconCls : 'b-icon b-fa-user' }
]
},
{
id : 2,
name : 'Roxette',
iconCls : 'b-icon b-fa-users',
born : null,
children : [
{ id : 21, name : 'Per', born : 1959, iconCls : 'b-icon b-fa-user' },
{ id : 22, name : 'Marie', born : 1958, iconCls : 'b-icon b-fa-user' }
]
}
],
columns : [
{ type : 'tree', field : 'name', text : 'Name', flex : 1 },
{ type : 'number', field : 'born', text : 'Born', flex : 1, format : '0' }
]
};
return (
<BryntumTreeGrid {...gridProps} ref={gridRef} />
);
}
We define the grid configs using the gridProps
variable and pass them in as props to the BryntumTreeGrid
wrapper component, which encapsulates the Bryntum TreeGrid. The config has some example inline tree data. The TreeGrid must be configured with one TreeColumn with type
set to tree
. The gridRef
is used to access the Bryntum TreeGrid instance.
Replace the styling in the src/app/globals.css
file with the following:
@import "@bryntum/grid/grid.stockholm.css";
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: Poppins, "Open Sans", Helvetica, Arial, sans-serif;
}
#app {
height: 100vh;
}
Run the local development server using the following command:
npm run dev
You’ll see a basic Bryntum Tree Grid:
Creating a custom GitHub issues data model
Let’s create a custom Bryntum Grid data model for the GitHub issues. Create a lib
folder in the src
folder. Create a GitHubIssueModel.ts
file in the lib
folder and add the following lines of code to it:
import { GridRowModel } from '@bryntum/grid';
export class GitHubIssueModel extends GridRowModel {
static get fields() {
return [
'title',
{
name : 'number',
type : 'number'
},
'author'
];
}
}
The modelClass
is used to define the model fields in the data store. The simplified GitHubIssue
custom class for GitHub issues extends the default GridRowModel
.
Creating a GitHub issue store with lazy loading
Create a storeConfig.ts
file in the src
folder and add the following lines of code to it:
import { AjaxStore } from '@bryntum/grid';
import { GitHubIssueModel } from './lib/GitHubIssueModel';
export function createGitHubIssueStore(
): AjaxStore {
return new AjaxStore({
tree : true,
modelClass : GitHubIssueModel,
readUrl : '/api/read',
lazyLoad : {
chunkSize : 100 // default value
},
autoLoad : true
});
}
We use a function because we’ll pass in a React state variable to the store config later. A Store is a data container that holds the grid data. An AjaxStore
is a store that uses the Fetch API to read data and sync data changes to a remote server. We set the API route URL that we’ll read data from using the readURL
property. Setting the autoLoad
property to true
means that the data is fetched on page load. We’ll create a Next.js API route for the read URL.
The lazyLoad
property enables the lazy loading of data. The number of records fetched on each lazy load is the “chunk size”. The store keeps track of what data has been loaded, and when a user scrolls far enough down the grid, the next chunk of data is fetched.
When a fetch request is made from a tree grid, startIndex
, count
, and parentId
are added as URL parameters to the request. The startIndex
parameter is the index where the server should start reading data from the database and count
is the chunkSize
. If requests are made from the top level of the tree grid, the parentId
is root
.
Creating the Bryntum TreeGrid configuration file
Create a gridConfig.ts
file in the src
folder and add the following lines of code to it:
import { AjaxStore, StringHelper } from '@bryntum/grid';
import { BryntumTreeGridProps } from '@bryntum/grid-react';
type GridConfigOptions = {
store: AjaxStore;
};
export function createGridConfig(
{ store }: GridConfigOptions
): BryntumTreeGridProps {
return {
animateTreeNodeToggle : true,
summaryFeature : true,
cellTooltipFeature : {
hoverDelay : 300,
textContent : true,
tooltipRenderer : ({ record }) => {
if (record.getData('number')) return null;
return StringHelper.encodeHtml(record.getData('title'));
}
},
sortFeature : false,
readOnly : true,
store,
columns : [
{
type : 'number',
field : 'number',
text : 'Issue #',
width : 180,
filterable : false,
tooltipRenderer : false,
sum : 'count',
summaryRenderer : ({ sum }) => `Total loaded: ${sum}`
},
{
id : 'IssuesAndComments',
type : 'tree',
field : 'title',
flex : 1
},
{
field : 'author',
text : 'Author',
width : 210,
filterable : false,
tooltipRenderer : false
}
],
tbar : [
{
type : 'container',
style : StringHelper.xss`width: 500px; color: black;`,
items : [{
type : 'display',
value : 'GitHub Issues and comments for the VS Code repository'
}]
},
]
};
}
We pass the lazy-loading data store to the grid config. When lazy loading data, remote sorting and filtering is enabled and local sorting and filtering is disabled. We disable the sortFeature, as we won’t implement it in this tutorial. The summaryFeature lets us see the number of issues loaded at the bottom of the number column. The cellTooltipFeature lets us see long issue titles in a tooltip.
The tbar
configures the Toolbar displayed at the top of the Grid, which reads, “GitHub Issues and comments for the VS Code repository”.
Adding the Bryntum Grid config to the Bryntum TreeGrid
Replace the code in the src/components/Grid.tsx
file with the following lines of code:
import { useMemo, useRef } from 'react';
import { createGridConfig } from '@/gridConfig';
import { createGitHubIssueStore } from '@/storeConfig';
import { BryntumTreeGrid } from '@bryntum/grid-react';
export default function Grid() {
const gridRef = useRef<BryntumTreeGrid>(null);
const store = useMemo(() =>
createGitHubIssueStore()
,[]);
const gridProps = useMemo(() =>
createGridConfig({ store }),
[store]
);
return (
<BryntumTreeGrid {...gridProps} ref={gridRef} />
);
}
We use the createGitHubIssueStore
and createGridConfig
functions to create the grid configs that are passed to the Bryntum TreeGrid as props. The store
and gridProps
are memoized to prevent unnecessary recreations of these complex objects on rerenders. This optimization ensures they’re only regenerated when their dependencies change, improving performance and preventing potential issues with event listeners.
Creating a Next.js read API route to return data chunks from the GitHub GraphQL API
In the src/app/
folder, create an api
folder. In api
, create a read
folder. In read
, create a route.ts
file and add the following lines of code to it:
import { type NextRequest } from 'next/server';
type Variables = {
first: number;
queryString?: string;
after?: string;
issueId?: string | null;
};
type GitHubIssue = {
id: string;
number: number;
title: string;
author?: {
login: string;
} | null;
comments: {
totalCount: number;
};
}
type IssueEdge = {
node: GitHubIssue;
}
type IssueData = {
id: string;
number: number;
author: string;
title: string;
children: boolean;
remoteChildCount: number;
}
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const count = searchParams.get('count');
const filters = searchParams.get('filters');
const endCursor = searchParams.get('endCursor') || 'null';
const parentId = searchParams.get('parentId');
if (!count) {
return Response.json({
success : false,
message : 'Missing count parameter'
}, {
status : 400
});
}
const githubRepoOwner = 'microsoft';
const githubRepoName = 'vscode';
let query = '';
const variables: Variables = {
first : parseInt(count) || 100
};
const baseQuery = `repo:${githubRepoOwner}/${githubRepoName} is:issue`;
variables.queryString = baseQuery;
// Lazyload issues
if (!filters && parentId === 'root') {
const afterStr = endCursor !== 'null' && endCursor !== 'undefined' ? `${endCursor}` : '';
variables.after = afterStr;
query = `
query ($first: Int!, $after: String) {
repository(owner: "${githubRepoOwner}", name: "${githubRepoName}") {
issues(first: $first, after: $after) {
totalCount
edges {
node {
id
author {
login
}
number
title
comments {
totalCount
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
`;
try {
const response = await fetch('https://api.github.com/graphql', {
method : 'POST',
headers : {
'Content-Type' : 'application/json',
'Authorization' : `bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`
},
body : JSON.stringify({ query, variables })
});
const rawData = await response.json();
const issues = rawData.data.repository.issues;
let newEndCursor = null;
if (issues.pageInfo.hasNextPage) {
newEndCursor = issues.pageInfo.endCursor;
}
const data: IssueData[] = issues.edges.map((edge: IssueEdge): IssueData => {
const issue: GitHubIssue = edge.node;
return {
id : issue.id,
number : issue.number,
author : issue?.author ? issue.author.login : 'Unknown',
title : issue.title,
children : issue.comments.totalCount > 0,
remoteChildCount : issue.comments.totalCount
};
});
return Response.json({
data,
total : issues.totalCount,
endCursor : newEndCursor
});
}
catch (error) {
console.error(error);
return Response.json({
success : false,
message : 'Failed to fetch issues'
}, {
status : 400
});
}
}
}
The GET request handler gets the URL parameters sent by the Bryntum TreeGrid using the searchParams.get()
method.
If no filters are applied and parentId
is root
, a GraphQL query string gets the first 100 issues from the VS Code GitHub repository, just as we did in the GitHub GraphQL API Explorer. The fetch
API is used to make a query to the GitHub GraphQL API using the query string and the variables for the query. The GitHub personal access token is added to the 'Authorization'
header using an environment variable.
The server returns an object with a data
property containing the GitHub issues. The total
property is the total number of records in the database, which the Bryntum TreeGrid uses to determine when to stop making requests for data as the user nears the bottom of the grid. The endCursor
property is used for pagination. We’ll create a React ref in the client-side Grid
component to save the endCursor
value, which we’ll send with each load request.
The returned data also includes a remoteChildCount
field for each issue, indicating the number of comments. When using a tree store, each parent must have a remoteChildCount
field containing the count of all the parent’s children (including nested grandchildren, if there are any).
Open http://localhost:3000. You should see a Bryntum TreeGrid with the first 100 issues of the VS Code GitHub repository:
If you scroll down, a GET request is made for the next 100 issues but it fails to load because we have not saved the new endCursor
value on the client and sent it with the next lazy-load request. Let’s get the lazy loading working now.
Saving the pagination end cursor in the Bryntum TreeGrid component
Add the following import to the src/storeConfig.ts
file:
import { RefObject } from 'react';
Add the following endCursorRef
parameter to the createGitHubIssueStore
function:
endCursorRef: RefObject<string | null>
Add the following event handler properties to the returned AjaxStore
:
onBeforeLoad(event) {
(event.params as
{
endCursor: string | null;
}
).endCursor = endCursorRef.current;
},
onAfterRequest({ response }) {
const res = response as OnAfterRequest;
// end cursor for lazy loading issues
if (res.parsedJson.commentEndCursor === undefined) {
endCursorRef.current = res.parsedJson.endCursor;
}
},
Add the OnAfterRequest
type to the top of the file:
type OnAfterRequest = Response & {
parsedJson: {
endCursor: string;
commentEndCursor: string;
total: number;
};
};
The onBeforeLoad
event handler is used to add the endCursorRef
value as a parameter to the load request. The onAfterRequest
event handler is used to set the endCursorRef
value to the new endCursor
value returned by the load API route.
In src/components/Grid.tsx
file, add the following React ref to the Grid
component:
const endCursorRef = useRef<string | null>(null);
TheendCursor
value will be saved in this ref.
Add the endCursorRef
as an argument to the createGitHubIssueStore
function used to create the store
:
const store = useMemo(() =>
createGitHubIssueStore(
endCursorRef
), []);
Now when you scroll down the TreeGrid, more issues will be fetched from the GitHub GraphQL API:
Using lazy-load event handlers to indicate loading state
The lazy load feature has two lazy-load-specific event handlers:
- The
lazyLoadStarted
event handler is called when the store starts loading new chunks of data. - The
lazyLoadEnded
event handler is called when the new chunk of data is finished being loaded into the grid’s store.
We’ll use these event handlers in the store
to set a React state variable that stores the lazy-loading state.
Import the useState
React Hook in src/components/Grid.tsx
file:
import { useState } from 'react';
Add the following state variable declaration to the Grid
component:
const [networkValue, setNetworkValue] = useState<NetworkValueTypes>({
text : 'Idle',
color : 'green'
});
Add the following type for the state object at the top of the file:
export type NetworkValueTypes = {
text: 'Idle' | 'Loading' | 'Committing';
color: 'green' | 'blue' | 'red';
};
Add the state setter setNetworkValue
as an argument to the createGitHubIssueStore
function call:
const store = useMemo(() =>
createGitHubIssueStore(
endCursorRef,
setNetworkValue // <---
),
[]
);
Add the networkValue
state in the object argument of the createGridConfig
function call and add networkValue
to the gridProps
useMemo
dependency list.
const gridProps = useMemo(() =>
createGridConfig({
networkValue, // <---
store }),
[networkValue, store] // <---
);
In the src/storeConfig.ts
file, import NetworkValueTypes
:
import { NetworkValueTypes } from './components/Grid';
Add the setNetworkValue
state setter as a parameter in the createGitHubIssueStore
function:
setNetworkValue: (value: NetworkValueTypes) => void
Add the following listeners property to the AjaxStore
:
listeners : {
lazyLoadStarted() {
setNetworkValue({
text : 'Loading',
color : 'blue'
});
},
lazyLoadEnded() {
setNetworkValue({
text : 'Idle',
color : 'green'
});
}
},
In the src/gridConfig.ts
file, import NetworkValueTypes
:
import { NetworkValueTypes } from './components/Grid';
Add networkValue
to the GridConfigOptions
type:
type GridConfigOptions = {
networkValue: NetworkValueTypes;
store: AjaxStore;
};
Pass networkValue
as a parameter to the createGridConfig
function:
networkValue,
Add the following item to the tbar
config array:
{
type : 'container',
style : StringHelper.xss`width: 220px; margin-left: auto; color: ${networkValue.color}`,
items : [{
type : 'display',
value : `Network status: ${networkValue.text}`
}]
}
The Bryntum TreeGrid toolbar will now display the lazy-loading state:
Dynamically loading child nodes: Issue comments
When a tree parent node is expanded by clicking the right arrow on the left side of a row, a load request for the child nodes is made. The parentId
in the load request parameters is the id
of the parent node. Let’s first update the read API route to lazy load issue comments.
Creating a GraphQL query to lazy load issue comments in the read API route.
In the src/app/api/read/route.ts
file, add the following line of code at the top of the GET request handler to get the commentEndCursor
URL parameter that we’ll send using the Bryntum TreeGrid:
const commentEndCursor = searchParams.get('commentEndCursor');
Add the following if
block below the if
block where there are no filters and the parentId
is root
:
// Dynamically load comments
if (parentId !== 'root') {
variables.issueId = parentId;
// Use commentEndCursor if valid; otherwise, start fresh
const effectiveCommentEndCursor = (commentEndCursor && commentEndCursor !== 'null' && commentEndCursor !== 'undefined')
? commentEndCursor
: null;
if (effectiveCommentEndCursor) {
variables.after = effectiveCommentEndCursor;
}
query = `
query ($issueId: ID!, $first: Int!, $after: String) {
node(id: $issueId) {
... on Issue {
comments(first: $first, after: $after) {
totalCount
edges {
node {
id
body
createdAt
author {
login
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
}
`;
try {
const response = await fetch('https://api.github.com/graphql', {
method : 'POST',
headers : {
'Content-Type' : 'application/json',
'Authorization' : `bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`
},
body : JSON.stringify({ query, variables })
});
const rawData = await response.json();
const comments = rawData.data.node.comments;
let newCommentEndCursor = null;
if (comments.pageInfo.hasNextPage) {
newCommentEndCursor = comments.pageInfo.endCursor;
}
const data: MappedComment[] = comments.edges.map((edge: CommentEdge): MappedComment => {
const comment: Comment = edge.node;
return {
id : comment.id,
author : comment?.author ? comment.author?.login : 'Unknown',
title : comment.body
};
});
return Response.json({
data,
commentEndCursor : newCommentEndCursor
});
}
catch (error) {
console.error(error);
return Response.json({
success : false,
message : 'Failed to fetch comments'
}, {
status : 400
});
}
}
Add the following types to the top of the file:
type Comment = {
id: string;
body: string;
createdAt: string;
author?: {
login: string;
} | null;
}
type CommentEdge = {
node: Comment;
}
type MappedComment = {
id: string;
author: string;
title: string;
}
We use commentEndCursor
as a query variable to query the issue comments. The comments query fetches a paginated list of comments for a specific issue using the issue id
. Because the returned node could be of any type, the ... on Issue
inline fragment is used to ensure the node is treated as an issue. This allows you to query issue-specific fields such as comments
.
The newCommentEndCursor
variable value is set using pageInfo.endCursor
and returned in the response as the commentEndCursor
property.
Saving a pagination end cursor for comments in the React Bryntum TreeGrid component
In src/components/Grid.tsx
file, add the following React ref to the Grid
component:
const commentEndCursorRef = useRef<string | null>(null);
This React ref saves the end cursor for comment queries.
Pass the two end cursor refs as arguments to the createGitHubIssueStore
function call:
gridRef,
commentEndCursorRef,
In the src/storeConfig.ts
file, add the following import:
import { BryntumTreeGrid } from '@bryntum/grid-react';
Add the following parameters to the createGitHubIssueStore
function:
gridRef: RefObject<BryntumTreeGrid | null>,
commentEndCursorRef: RefObject<string | null>,
Replace the onBeforeLoad
and onAfterRequest
event handlers with the following:
onBeforeLoad(event) {
(event.params as
{
endCursor: string | null;
commentEndCursor: string | null
}
).endCursor = endCursorRef.current;
(event.params as {
endCursor: string | null;
commentEndCursor: string | null
}).commentEndCursor = commentEndCursorRef.current;
},
onAfterRequest({ response }) {
const res = response as OnAfterRequest;
// end cursor for lazy loading issues
if (res.parsedJson.commentEndCursor === undefined) {
endCursorRef.current = res.parsedJson.endCursor;
}
// end cursor for lazy loading comments
else {
commentEndCursorRef.current = res.parsedJson.commentEndCursor;
}
if (gridRef.current?.instance && res.parsedJson.total) {
// @ts-expect-error getById is not in the type definition
const column = gridRef.current.instance.columns.getById('IssuesAndComments');
if (column) {
column.set({
text : `Total issues: ${res.parsedJson.total}`
});
}
}
},
The onBeforeLoad
event handler is used to add the end cursor refs as parameters in the load request. The onAfterRequest
event handler is used to set the end cursor ref values to the new values returned by the load API route.
These two end cursors let you lazy load comments and issues:
Remote filtering parent data: Issues
Filtering can be configured using the filterFeature
. Add the following filterFeature
property to the grid config returned by the createGridConfig
function in the src/gridConfig.ts
file:
filterFeature : {
allowedOperators : ['includes'],
pickerConfig : {
showAddFilterButton : false
}
},
The allowedOperators
config lets you limit the allowed operators that populate the filtering sub-menus. For simplicity, we limit the allowed operators to 'includes'
only.
When remote filtering is enabled, the filter information is added to load request parameters.
In the src/storeConfig.ts
file, add the following property to the AjaxStore
:
filterParamName : 'filters',
The filterParamName
property is the parameter name used to pass filters when loading remote data.
To add remote filtering functionality to our API route, add the following if
block to the GET request handler in the src/app/api/read/route.ts
file:
// Filter issues
if (filters && parentId === 'root') {
const parsedFilter = JSON.parse(filters);
const filterObj = parsedFilter[0];
if (filterObj.field === 'title' && filterObj.operator === 'includes') {
variables.queryString += ` ${filterObj.value} in:${filterObj.field}`;
}
// Use endCursor if valid; otherwise, start fresh
const effectiveEndCursor = (endCursor && endCursor !== 'null' && endCursor !== 'undefined')
? endCursor
: null;
if (effectiveEndCursor) {
variables.after = effectiveEndCursor;
}
query = `
query ($queryString: String!, $first: Int!, $after: String) {
search(query: $queryString, type: ISSUE, first: $first, after: $after) {
issueCount
edges {
node {
... on Issue {
id
number
title
author {
login
}
comments {
totalCount
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;
try {
const response = await fetch('https://api.github.com/graphql', {
method : 'POST',
headers : {
'Content-Type' : 'application/json',
'Authorization' : `bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`
},
body : JSON.stringify({ query, variables })
});
const rawData = await response.json();
const issues = rawData.data.search;
let newEndCursor = null;
if (issues.pageInfo.hasNextPage) {
newEndCursor = issues.pageInfo.endCursor;
}
const data: IssueData[] = issues.edges.map((edge: IssueEdge): IssueData => {
const issue: GitHubIssue = edge.node;
return {
id : issue.id,
number : issue.number,
author : issue?.author ? issue.author.login : 'Unknown',
title : issue.title,
children : issue.comments.totalCount > 0,
remoteChildCount : issue.comments.totalCount
};
});
return Response.json({
data,
total : issues.issueCount,
endCursor : newEndCursor
});
}
catch (error) {
console.error(error);
return Response.json({
success : false,
message : 'Failed to fetch issues'
}, {
status : 400
});
}
}
Filtering is done using a search
query. The query
argument in search
is the search string to look for. The GitHub GraphQL API has search syntax that allows you to create queries that match specific numbers and words. For example, the following search query string has an in
qualifier that restricts the search to issues with a title that includes the word “undefined”:
"repo:microsoft/vscode is:issue undefined in:title"
To see filtering in action, hover over the title column and click the filter icon on the right side of the title column to open the filter popup:
Next steps
To learn more about using Bryntum products with GraphQL, check out our blog post, Create a scheduler using Bryntum, Next.js, Prisma, SQLite, and GraphQL.
You can also learn how to implement remote sorting in the first article of this two-part series on lazy loading in Bryntum Grid: Infinite data Grid scrolling with the new lazy loading feature.