DEV Community

Cover image for Github's GraphQl API with react-query and NextJs
Dennis kinuthia
Dennis kinuthia

Posted on

4

Github's GraphQl API with react-query and NextJs

working with Github's Graphql api

After working with the github rest api and experiencing some of it's limitations it's time to pick things up a bit and dive into their Graphql api

as Usual first things first we set up a testing environment and github provides their own explorer which will setup the authentication headers for you under the hood , but if you prefer to use another gql explorer , just set it up like below
Replace ghp_LBgN6pCeWsHcxeoJpR4ZTwUj with your personal access token

Image description

I have another article i made about react-query tips and tricks which might help explain that part . because i want to focus on the actual GraphQl queries in this one i'll mostly highlight the queries i used in the project and not go into details about the react part

final project live preview

let go

before we start we'll define a graphql Fragment that defines a User, this will come in handy because we'll be using it when querying the viewer ,user, follower , and following for uniformity so that if we need to add or remove a field we just do it once in the fragment and all the items defined by it change along with it.

import gql from "graphql-tag";

/// user fragments
export const OneUserFrag = gql`
  fragment OneUser on User {
    id
    name
    login
    email
    bio
    avatarUrl
    company
    twitterUsername
    createdAt
    isFollowingViewer
    viewerIsFollowing
    isViewer
    location
    url
    followers(first: 1) {
      totalCount
      nodes {
        id
      }
    }
    following(first: 1) {
      totalCount
      nodes {
        id
      }
    }

  repositories(first:1){
   totalCount
    nodes{
      id
    }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

If you remember the REST api was lacking a few fields in the equivalent query isFollowingViewer ,
viewerIsFollowing ,
isViewer

which will help us avoid having to run other sub queries to check the follow status on every follower/following item

viewer

This query returns the currently logged in User , and I am using the personal access token to authenticate users since it's the simplest to implement.

//get currently logged in user
export const GETVIEWER = gql`
  query getViewer {
    viewer {
      ...OneUser
    }
  }
  ${OneUserFrag}
`;
Enter fullscreen mode Exit fullscreen mode

you'll notice that We're only fetching one follower , following , repository

      following(first: 1) {
        totalCount
        nodes {
          id
        }
      }
Enter fullscreen mode Exit fullscreen mode

and that's because we're only interested in the totalCount field at this point in order to diply it like this with all the counts

Image description

paginated fields

This is also because those three field require pagination and take the first ,last , after and before parameters

last and first is number of nodes and from which portion you want them from start of end and before and after are cursors , the api doe's generate cursors for us and can be accessed inside the page info field that's available in every paginated field

export const getUserWithFollowers = gql`
  query getUserFollowers($login: String!, $first: Int, $after: String) {
    user(login: $login) {
     followers(first: $first, after: $after) {
        pageInfo {
          endCursor
          hasNextPage
          hasPreviousPage
          startCursor
        }
        totalCount
        edges {
          node {
      ...OneUser
          }
        }
      }
    }
  }
   ${OneUserFrag}
`;
Enter fullscreen mode Exit fullscreen mode

in this example to fetch the next values , we'll pass in the endCursor into the after field on the next query

also note the GraphQl types

$login: String!, $first: Int, $after: String
Enter fullscreen mode Exit fullscreen mode

where String! is required and cannot be null , but String can be null , which is why the in initial query you can pass in after as null

This is also an example of graphql variables , where

  query getUserFollowers($login: String!, $first: Int, $after: String) {}
Enter fullscreen mode Exit fullscreen mode

wiltake in the variables login ,first and after
and pass them into the query

user(login: $login) {
followers(first: $first, after: $after) {
Enter fullscreen mode Exit fullscreen mode

and with queries with no variables you'll just write

   query getViewer {
    viewer {}
    }
Enter fullscreen mode Exit fullscreen mode

i've found it easier to avoid stuffing multiple paginated fields into one query and just break them of into smaller queries to be run by their own component which instead of a giant query to be rendered in one component

for example , once the viewer has been fetched the smaller components nested in the main component will have their own queries
one for repositories another for followers and following
they will be optionally rendered in a tab like way where by default it'll load the repository tab and the others will be shown if the user explicitly clicks on them which is when they'll run their sub query

User

similar to the viewer query but this will take one login (username) as a parameter and return the OneUserFragMent , useful when you want to navigate to another User obtained either from the follower list or Search results

export const GETONEUSER = gql`
  query getUser($login: String!) {
    user(login: $login) {
      ...OneUser
      }
   }
   ${OneUserFrag}
Enter fullscreen mode Exit fullscreen mode

Search

we'll also want the ability to search for random github user's by their username or password

export const USERSEARCH = gql `

query userSearch($query:String!,$first:Int,$type:SearchType!){
  search(query:$query,first:$first,type:$type){
    repositoryCount
    discussionCount
    userCount
    codeCount
    issueCount
    wikiCount

  edges{
    node{
  ... on User {
    login
    name
    email
    avatarUrl
    url
    }
  }
  }
 }
}

`
Enter fullscreen mode Exit fullscreen mode

the syntax below is better explained here but i short it lets us access items of a specific fragment since this query can return fragments of different types User , Repository , Code, Issue....

 ... on User {
Enter fullscreen mode Exit fullscreen mode

which allows you to write highly customizable queries like this

query userSearch($query:String!,$first:Int,$type:SearchType!){
  search(query:$query,first:$first,type:$type){
    repositoryCount
    discussionCount
    userCount
    codeCount
    issueCount
    wikiCount

  edges{
    node{
  ... on User {
    login
    name
    email
    avatarUrl
    url
    }
     ... on Repository{
      name
      url
      }
      ... on  Issue{
        id
        body
      }
  }
  }
 }
}
Enter fullscreen mode Exit fullscreen mode

follower

the follower query is basically this query

//get currently logged in user
export const GETVIEWER = gql`
  query getViewer {
    viewer {
      ...OneUser
    }
  }
  ${OneUserFrag}
`;
Enter fullscreen mode Exit fullscreen mode
export const GETONEUSER = gql`
  query getUser($login: String!) {
    user(login: $login) {
      ...OneUser
      }
   }
   ${OneUserFrag}
Enter fullscreen mode Exit fullscreen mode

but with the OneUser fragment being requested inside the the follower field

export const getUserWithFollowers = gql`
  query getUserFollowers($login: String!, $first: Int, $after: String) {
    user(login: $login) {
     followers(first: $first, after: $after) {
        pageInfo {
          endCursor
          hasNextPage
          hasPreviousPage
          startCursor
        }
        totalCount
        edges {
          node {
      ...OneUser
          }
        }
      }
    }
  }
   ${OneUserFrag}
`;
Enter fullscreen mode Exit fullscreen mode

the following field is also very similar

export const getUserWithFollowing = gql`
  query getUserFollowing($login: String!, $first: Int, $after: String) {
    user(login: $login) {
    following(first: $first, after: $after) {
        pageInfo {
          endCursor
          hasNextPage
          hasPreviousPage
          startCursor
        }
        totalCount
        edges {
          node {
       ...OneUser
          }
        }
      }
    }
  }
  ${OneUserFrag}
`;
Enter fullscreen mode Exit fullscreen mode

Repository

this field has a lot on it and it'll be all about what you want to display in your app
in my case i wanted to display something like this

Image description

I used ben awad's profile because his repositories actually have stars ,forks and multiple languages which is the brief info i want to see at a glance before i decide to click on it and see more

to achieve this i used this query

export const REPOS = gql`
         query getRepos($name: String!, $first: Int, $after: String) {
           user(login: $name) {
             login
             repositories(
               after: $after
               first: $first
               orderBy: { field: PUSHED_AT, direction: DESC }
             ) {
               edges {
                 node {
                   id,
                   name,
                   description,
                   pushedAt,
                   diskUsage,
                   url,
                   visibility,
                   forkCount,
                   stargazers(first: $first) {
                   totalCount
                   },
                   refs(
                     refPrefix: "refs/heads/"
                     orderBy: { direction: DESC, field: TAG_COMMIT_DATE }
                     first: 2
                   ) {
                     edges {
                       node {
                         name
                         id
                         target {
                           ... on Commit {
                             history(first: 1) {
                               edges {
                                 node {
                                   committedDate
                                   author {
                                     name
                                   }
                                   message
                                 }
                               }
                             }
                           }
                         }
                       }
                     }
                   }

                   languages(first: $first) {
                     edges {
                       node {
                         id
                         color
                         name
                       }
                     }
                   }
                 }
                 cursor
               }
               totalCount
               pageInfo {
                 startCursor
                 endCursor
                 hasNextPage
                 hasPreviousPage
               }
             }
           }
         }
       `;

Enter fullscreen mode Exit fullscreen mode

which is one moderately chonky query but returns everything i need in one query,

     refs(
                     refPrefix: "refs/heads/"
                     orderBy: { direction: DESC, field: TAG_COMMIT_DATE }
                     first: 2
                   ) {
                     edges {
                       node {
                         name
                         id
                         target {
                           ... on Commit {
                             history(first: 1) {
                               edges {
                                 node {
                                   committedDate
                                   author {
                                     name
                                   }
                                   message
                                 }
                               }
                             }
                           }
                         }
                       }
                     }
                   }
Enter fullscreen mode Exit fullscreen mode

in this bit am requesting for the 2 most recent commits and the branch on which it was made , the fact that this is possible in one query blows my mind which is another reason i really like graphql

but to top it all off am planning to implement a bigger query which i abandoned after realising it would be a pagination nightmare and would be better off being split up into smaller queries and each query being assigned it's child component which an be optionally rendered on user request but here it is anyway

const FULLREPO = gql`
# github graphql query to get more details  
  query getRepoDetails(
    $repoowner: String!,
    $reponame: String!,
    $first: Int,
    $after: String,
  ) {
    repository(owner: $repoowner, name: $reponame) {
    nameWithOwner,

      # get the repo collaborators

      collaborators(first: $first, after: $after) {
        edges {
          node {
            avatarUrl,
            email,
            name,
            bio,
            company
          },
        },
        pageInfo {
          endCursor,
          hasNextPage,
          hasPreviousPage,
          startCursor
        },
        totalCount
      }
      # end of collaborators

      # gets the repo vulnerabilities

      vulnerabilityAlerts(first: $first, after: $after) {
        edges {
          node {
            createdAt,
            securityAdvisory {
              classification,
              description,
              vulnerabilities(first: $first, after: $after) {
                edges {
                  node {
                    severity,
                    package {
                      name,
                      ecosystem
                    }
                  }
                },
                pageInfo {
                  endCursor
                  hasNextPage
                  hasPreviousPage
                  startCursor
                },
                totalCount
              }
            }
          }
        }
      },
      #   end of vulnerabilities block

      #refs: get branches and all the recent commits to it

      refs(
        refPrefix: "refs/heads/"
        orderBy: { direction: DESC, field: TAG_COMMIT_DATE }
        first: $first
        after: $after
      ) {
        edges {
          node {
            name
            id
            target {
              ... on Commit {
                history(first: $first, after: $after) {
                  edges {
                    node {
                      committedDate,
                      author {
                        name,
                        email
                      },
                      message,
                      url,
                      pushedDate,
                      authoredDate,
                      committedDate
                    }
                  }
                }
              }
            }
          }
        },
        pageInfo {
          endCursor,
          hasNextPage,
          hasPreviousPage,
          startCursor,
        },
        totalCount
        nodes {
          associatedPullRequests(first: $first, after: $after) {
            pageInfo {
              endCursor,
              hasNextPage,
              hasPreviousPage,
              startCursor,
            },
            totalCount
          }
        }
      }

      # end of refs block
      # languages
      languages(first: $first, after: $after) {
        edges {
          node {
            id,
            color,
            name
          }
        },
        pageInfo {
          endCursor,
          hasNextPage,
          hasPreviousPage,
          startCursor
        },
        totalCount
      }

      # end of languages block
      forkCount
      #fork block
      forks(first: $first, after: $after) {
        edges {
          node {
            createdAt,
            nameWithOwner,
            description,
            url,
            owner {
              login,
              url
            },
            parent {
              url,
              owner {
                login,
                url
              }
            }
          }
        }
        pageInfo {
          endCursor,
          hasNextPage,
          hasPreviousPage,
          startCursor,
        },
        totalCount
      }
      # end of fork block

      # star block
      stargazers(first: $first, after: $after) {
        edges {
          node {
            login,
            url
          }
        }
        pageInfo {
          endCursor,
          hasNextPage,
          hasPreviousPage,
          startCursor
        }
        totalCount
      }
      #end of star block
    }
  }
`;

Enter fullscreen mode Exit fullscreen mode

the query works fine , but only if you don't paginate because then you'll have to add more after variable for every paginated field and also handle the react-query / your gql client of choice

btw , check out example usage of react-query with graphql

final project live preview
next js usage
react-query tips and tricks
github rest api

react-query tips and tricks i've found usefull while working with github's graphql api

using select to insert a new value into the response

import React from 'react'
import { useQuery } from 'react-query';
import { GraphQLClient } from 'graphql-request';
import { gql } from 'graphql-tag';

interface TestProps {
token:string
}

interface User{
  user:Root
}
export interface Root {
  login: string;
  id: string;
  isFollowingViewer: boolean;
  viewerIsFollowing: boolean;
  bio: string;
  avatarUrl: string;
  isViewer: boolean;
  url: string;
}

export const Test: React.FC<TestProps> = ({token}) => {  
const username = 'tigawanna'
const key =['user']

 const USER = gql`
         query getMiniUser($name: String!) {
           user(login: $name) {
             login
             id
             isFollowingViewer
             viewerIsFollowing
             bio
             avatarUrl
             isViewer
             url
           }
         }
       `;

   const endpoint = "https://api.github.com/graphql";
  const headers = {
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  };      
const graphQLClient = new GraphQLClient(endpoint, headers);
const fetchData = async () => await graphQLClient.request(USER,{name:"tigawanna"});

const query = useQuery(key, fetchData,
{
enabled: username.length > 3,
  select: (data:User) => {
 let newdata   
  if (data.user.isViewer){
  newdata = {...data.user,OP:true}
  return {user:newdata}
  }
  return data
  }
}
)

console.log("test query === ",query.data)



return (
 <div className='w-full h-full '>

HELLO
 </div>
);}

using select to filter the response array

import React from 'react'
import { useQuery } from 'react-query';
import { GraphQLClient } from 'graphql-request';
import { gql } from 'graphql-tag';

interface TestProps {
token:string
}

interface User{
  user:Root
}
export interface Followers {
  edges: Edge[];
}

export interface Edge {
  node: Node;
}

export interface Node {
  login: string;
  avatarUrl: string;
  id: string;
}
export interface Root {
  login: string;
  id: string;
  isFollowingViewer: boolean;
  viewerIsFollowing: boolean;
  bio: string;
  avatarUrl: string;
  isViewer: boolean;
  url: string;
  followers: Followers;
}

export const Test: React.FC<TestProps> = ({token}) => {  
const username = 'tigawanna'
const key =['user']

 const USER = gql`
         query getMiniUser($name: String!) {
           user(login: $name) {
             login
             id
             isFollowingViewer
             viewerIsFollowing
             bio
             avatarUrl
             isViewer
             url
          followers(first:10) {
             edges {
                 node {
                   login
                   avatarUrl
                   id
                 }
               }
               }
           }
         }
       `;

   const endpoint = "https://api.github.com/graphql";
  const headers = {
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  };      
const graphQLClient = new GraphQLClient(endpoint, headers);
const fetchData = async () => await graphQLClient.request(USER,{name:"tigawanna"});
const keyword = "a"
const query = useQuery(key, fetchData,
{
enabled: username.length > 3,
  select: (data:User) => {
 let newdata   
  if (keyword){
  const followers = data.user.followers.edges.filter((item)=>{
   return item.node.login.includes(keyword)
  })  
  newdata = {...data.user,followers}
  return {user:newdata}
  }
  return data
  }
}
)
console.log("test query === ",query.data)



return (
 <div className='w-full h-full '>

HELLO
 </div>
);}

onError and onSuccess side effects

import React from 'react'
import { useQuery } from 'react-query';
import { GraphQLClient } from 'graphql-request';
import { gql } from 'graphql-tag';

interface TestProps {
token:string
}

interface User{
  user:Root
}
export interface Followers {
  edges: Edge[];
}

export interface Edge {
  node: Node;
}

export interface Node {
  login: string;
  avatarUrl: string;
  id: string;
}
export interface Root {
  login: string;
  id: string;
  isFollowingViewer: boolean;
  viewerIsFollowing: boolean;
  bio: string;
  avatarUrl: string;
  isViewer: boolean;
  url: string;
  followers: Followers;
}

export const Test: React.FC<TestProps> = ({token}) => {  
const username = 'tigawanna'
const key =['user']

 const USER = gql`
         query getMiniUser($name: String!) {
           user(login: $name) {
             login
             id
             isFollowingViewer
             viewerIsFollowing
             bio
             avatarUrl
             isViewer
             url
          followers(first:10) {
             edges {
                 node {
                   login
                   avatarUrl
                   id
                 }
               }
               }
           }
         }
       `;

   const endpoint = "https://api.github.com/graphql";
  const headers = {
    headers: {
      Authorization: `Bearer ${token+'gg'}`,
      "Content-Type": "application/json",
    },
  };      
const graphQLClient = new GraphQLClient(endpoint, headers);
const fetchData = async () => await graphQLClient.request(USER,{name:"tigawanna"});
const keyword = "a"
const query = useQuery(key, fetchData,
{
enabled: username.length > 3,
  select: (data:User) => {
 let newdata   
  if (keyword){
  const followers = data.user.followers.edges.filter((item)=>{
   return item.node.login.includes(keyword)
  })  
  newdata = {...data.user,followers}
  return {user:newdata}
  }
  return data
  },
  onSuccess:(data:User)=>{
    console.log("success")
  },
  onError:(error:any)=>{
    console.log("error  = ",error.response)
    if(error?.response?.status === "401" || error?.response?.status === "402"){
      // invalidate my locally stored token to send me back to 
      // login screen,those codes mean tokenhas an issue
    }
  }
}
)
console.log("test query === ",query.data)



return (
 <div className='w-full h-full '>

HELLO
 </div>
);}

standard infinite query

import React from 'react'
import { useQuery } from 'react-query';
import { GraphQLClient } from 'graphql-request';
import { gql } from 'graphql-tag';
import { useInfiniteQuery } from 'react-query';

interface TestProps {
token:string
}


export interface RootResponse {
  pages: Page[];
  pageParams: any[];
}

export interface Page {
  user: User;
}

export interface User {
  login: string;
  id: string;
  isFollowingViewer: boolean;
  viewerIsFollowing: boolean;
  bio: string;
  avatarUrl: string;
  isViewer: boolean;
  url: string;
  followers: Followers;
}

export interface Followers {
  edges: Edge[];
  totalCount: number;
  pageInfo: PageInfo;
}

export interface Edge {
  node: Node;
}

export interface Node {
  login: string;
  avatarUrl: string;
  id: string;
}

export interface PageInfo {
  startCursor: string;
  endCursor: string;
  hasNextPage: boolean;
  hasPreviousPage: boolean;
}
export const Test: React.FC<TestProps> = ({token}) => {  
const username = 'tigawanna'
const key =['user']

 const USER = gql`
   query getMiniUser($name: String!, $first: Int!, $after: String) {
     user(login: $name) {
       login
       id
       isFollowingViewer
       viewerIsFollowing
       bio
       avatarUrl
       isViewer
       url
       followers(first: $first, after: $after) {
         edges {
           node {
             login
             avatarUrl
             id
           }
         }

         totalCount
         pageInfo {
           startCursor
           endCursor
           hasNextPage
           hasPreviousPage
         }

       }
     }
   }
 `;

   const endpoint = "https://api.github.com/graphql";
  const headers = {
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  };      
const graphQLClient = new GraphQLClient(endpoint, headers);
const fetchData = async (deps: any) => {
    const after = deps?.pageParam ? deps.pageParam : null;
    return await graphQLClient.request(USER, {
      name: "tigawanna",
      first: 10,
      after,
    });
  };

const keyword = "a"

const query = useInfiniteQuery(key, fetchData, {
  enabled: username.length > 3,
  onSuccess: (data: RootResponse) => {
    console.log("success", data);
  },
  onError: (error: any) => {
    console.log("error  = ", error.response);
    if (error?.response?.status === "401" || error?.response?.status === "402") {
      // invalidate my locally stored token to send me back to
      // login screen,those codes mean tokenhas an issue
    }
  },
  getPreviousPageParam: (firstPage: Page) => {
    return firstPage?.user?.followers?.pageInfo?.startCursor ?? null;
  },
  getNextPageParam: (lastPage: Page) => {
    return lastPage?.user?.followers?.pageInfo?.endCursor ?? null;
  },
});
console.log("test query === ",query.data)

  const pages = query?.data?.pages;
  //  console.log("followers === ",followers)
  //@ts-ignore
  const extras = pages[pages.length - 1].user?.followers;
  const hasMore = extras?.pageInfo?.hasNextPage;

return (
  <div className="w-full h-full ">
    <div className="h-fit w-full flex-center  flex-wrap">
      {pages?.map((page) => {
        return page?.user?.followers?.edges?.map((item) => {
          return (
            <div className='w-[30%] h-14 p-3 m-2 border border-green-500 '>
              user :{item.node.login}</div>
          );
        });
      })}
    </div>

    {!query.isFetchingNextPage && hasMore ? (
      <button
        className="m-2 hover:text-purple-400 shadow-lg hover:shadow-purple"
        onClick={() => {
          query.fetchNextPage();
        }}
      >
        --- load more ---
      </button>
    ) : null}
    {query.isFetchingNextPage ? (
      <div className="w-full flex-center m-1 p-1">loading more...</div>
    ) : null}
  </div>
);}

better infinite query

import React from 'react'
import { useQuery } from 'react-query';
import { GraphQLClient } from 'graphql-request';
import { gql } from 'graphql-tag';
import { useInfiniteQuery } from 'react-query';

interface TestProps {
token:string
}


export interface RootResponse {
  pages: Page[];
  pageParams: any[];
}

export interface Page {
  user: User;
}

export interface User {
  login: string;
  id: string;
  isFollowingViewer: boolean;
  viewerIsFollowing: boolean;
  bio: string;
  avatarUrl: string;
  isViewer: boolean;
  url: string;
  followers: Followers;
}

export interface Followers {
  edges: Edge[];
  totalCount: number;
  pageInfo: PageInfo;
}

export interface Edge {
  node: Node;
}

export interface Node {
  login: string;
  avatarUrl: string;
  id: string;
}

export interface PageInfo {
  startCursor: string;
  endCursor: string;
  hasNextPage: boolean;
  hasPreviousPage: boolean;
}
export const Test: React.FC<TestProps> = ({token}) => {  
const username = 'tigawanna'
const key =['user']

 const USER = gql`
   query getMiniUser($name: String!, $first: Int!, $after: String) {
     user(login: $name) {
       login
       id
       isFollowingViewer
       viewerIsFollowing
       bio
       avatarUrl
       isViewer
       url
       followers(first: $first, after: $after) {
         edges {
           node {
             login
             avatarUrl
             id
           }
         }

         totalCount
         pageInfo {
           startCursor
           endCursor
           hasNextPage
           hasPreviousPage
         }

       }
     }
   }
 `;

   const endpoint = "https://api.github.com/graphql";
  const headers = {
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  };      
const graphQLClient = new GraphQLClient(endpoint, headers);
// const fetchData = async () => await graphQLClient.request(USER,{name:"tigawanna",
// first:10,after:null
// });
const fetchData = async (deps: any) => {
    const after = deps?.pageParam ? deps.pageParam : null;
    return await graphQLClient.request(USER, {
      name: "tigawanna",
      first: 10,
      after,
    });
  };


const keyword = "a"

const query = useInfiniteQuery(key, fetchData, {
  enabled: username.length > 3,
  select:(data:RootResponse)=>{
  return concatFollowerPages(data,"")
  },
  onSuccess: (data: RootResponse) => {
    // console.log("success", data);
  },
  onError: (error: any) => {
    console.log("error  = ", error.response);
    if (error?.response?.status === "401" || error?.response?.status === "402") {
      // invalidate my locally stored token to send me back to
      // login screen,those codes mean tokenhas an issue
    }
  },
  getPreviousPageParam: (firstPage: Page) => {
    return firstPage?.user?.followers?.pageInfo?.startCursor ?? null;
  },
  getNextPageParam: (lastPage: Page) => {
    console.log("end cursor ", lastPage?.user?.followers?.pageInfo?.endCursor);
    return lastPage?.user?.followers?.pageInfo?.endCursor ?? null;
  },
});
// console.log("test query === ",query.data)


if(query.isFetching){
  return <div> loading .... </div>
}

const pages = query?.data?.pages;
return (
  <div className="w-full h-full ">
    <div className="h-fit w-full flex-center  flex-wrap">
      {pages?.map((page) => {
        return page?.user?.followers?.edges?.map((item, index) => {
          return (
            <div
              key={item.node.id}
              className="w-[30%] h-14 p-3 m-2 border border-green-500 text-lg font-bold"
            >
              {index} {item.node.login}
            </div>
          );
        });
      })}
    </div>

    {!query.isFetchingNextPage &&
    query?.data?.pages[0]?.user?.followers?.pageInfo?.hasNextPage ? (
      <button
        className="m-2 hover:text-purple-400 shadow-lg hover:shadow-purple"
        onClick={() => {
          query.fetchNextPage();
        }}
      >
        --- load more ---
      </button>
    ) : null}
    {query.isFetchingNextPage ? (
      <div className="w-full flex-center m-1 p-1">loading more...</div>
    ) : null}
  </div>
);}




export const concatFollowerPages = (data: RootResponse, keyword: string) => {

let totalRepos = data.pages[0].user.followers.edges;
  let i = 1;
  for (i = 1; i < data.pages.length; i++) {
    if (data?.pages) {
      totalRepos = [...totalRepos, ...data.pages[i].user.followers.edges];
    }
  }

  const filtered = totalRepos.filter((item) =>
    item.node.login.toLowerCase().includes(keyword.toLowerCase())
  );
  const base = data.pages[data.pages.length -1].user;
  const user = {
    ...base,
    login: base.login,
    followers: {
      edges: filtered,
      totalCount: base.followers.totalCount,
      pageInfo: base.followers.pageInfo,
    },
  };

  const final:RootResponse = {
    pageParams: [...data.pageParams],
    pages: [{ user: user }],
  };

  return final;
};

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more