Back to blogs
Blog
Writing in public
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Blog
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Back to blogs
Back to blogs
Postreactquery, react, graphql, typescript
One of the good things about React's opinionated nature is that it's allowed the community to come up...
Date published

slides used in the presentation building the generic component In this example we'll use...

Why You Might Still Pick Next.js Over TanStack Start From a TanStack Start...

A complete theme management system for React applications with SSR support! ✨ 🌟...
One of the good things about React's opinionated nature is that it's allowed the community to come up with some brilliant solutions and one of them is react-query now going as tanstack-query because they've expanded to the other frameworks
Should you use react-query , I'd say probably . if your ap is depends on making a lot of network requests you should consider it before reaching for something like redux
bear in mind that react query is opinionated about how you fetch your data , its job is handling the response and error/loading states plus cache and retries
Full code linked below , example usage in a project {% embed https://github.com/tigawanna/gitpals %} {% embed https://github.com/tigawanna/gitdeck %}
initialization is simple step 1 : create the actual async query function which returns a response or error
rest api i'll use axios , you can use fetch too
1const fetchdatas=async()=>{2const token ="token token token "3const url="https://api.github.com/user"4 const res = await axios({5 method: 'get',6 url: url,7 headers: {8 Authorization: `Bearer ${token}`,9 "Content-Type": "application/json"10 },11 })12 // //console.log("response == ",res)1314 const user = res.data15 // //console.log("authed user == ",user)16 return user17}
graphql api i'll use graphql-request
1 const USER = gql`2 query getMiniUser($name: String!) {3 user(login: $name) {4 login5 id6 isFollowingViewer7 viewerIsFollowing8 bio9 avatarUrl10 isViewer11 url12 }13 }14 `;15const graphQLClient = new GraphQLClient(endpoint, headers);16const fetchData = async () => await graphQLClient.request(USER,{name:"tigawanna);
step 2:connect it to a query hook in the case useQuery
1const key =['user']2useQuery(key, fetchData,);
here are some tips and tricks i keep reaching for in most of my projects
1const key =['user',name_variable]2useQuery(key, fetchData);
1const key =['user']2useQuery(key, fetchData,3{4enabled:name_variable !== "" && name_variable.length > 35);
1const query = useQuery(key, fetchData,2{3enabled: username.length > 3,4 select: (data:User) => {5 let newdata6 if (data.user.isViewer){7 newdata = {...data.user,OP:true}8 return {user:newdata}9 }10 return data11 }12}13)
the select option can also be used to reorder items , filter down an array of items without fetching the data
1const keyword = "a"2const query = useQuery(key, fetchData,3{4enabled: username.length > 3,5 select: (data:User) => {6 let newdata7 if (keyword){8 const followers = data.user.followers.edges.filter((item)=>{9 return item.node.login.includes(keyword)10 })11 newdata = {...data.user,followers}12 return {user:newdata}13 }14 return data15 }16}17)
1const query = useQuery(key, fetchData,2{3enabled: username.length > 3,4 select: (data:User) => {5 let newdata6 if (keyword){7 const followers = data.user.followers.edges.filter((item)=>{8 return item.node.login.includes(keyword)9 })10 newdata = {...data.user,followers}11 return {user:newdata}12 }13 return data14 },15 onSuccess:(data:User)=>{16 console.log("success")17//save token to local storage no issues with it18 },19 onError:(error:any)=>{20 console.log("error = ",error.response)21 if(error?.response?.status === "401" || error?.response?.status === "402"){22 // invalidate my locally stored token to send me back to23 // login screen,those codes mean tokenhas an issue24 }25 }26}27)
BONUS TIP : useInfiniteQuery hook
useInfiniteQuery hook is for handling pagination of long lists of data. For example in the graphql query we can pass in first and after params to a field like followers which could have a very long list
so we introduce pagination like so
1const query = useInfiniteQuery(key, fetchData, {2 enabled: username.length > 3,3 onSuccess: (data: RootResponse) => {4 console.log("success", data);5 },6 onError: (error: any) => {7 console.log("error = ", error.response);8 if (error?.response?.status === "401" || error?.response?.status === "402") {9 // invalidate my locally stored token to send me back to10 // login screen,those codes mean tokenhas an issue11 }12 },13 getPreviousPageParam: (firstPage: Page) => {14 return firstPage?.user?.followers?.pageInfo?.startCursor ?? null;15 },16 getNextPageParam: (lastPage: Page) => {17 return lastPage?.user?.followers?.pageInfo?.endCursor ?? null;18 },19});20console.log("test query === ",query.data)
and add a load more button
1 const pages = query?.data?.pages;2 // console.log("followers === ",followers)3 //@ts-ignore4 const extras = pages[pages.length - 1].user?.followers;5 const hasMore = extras?.pageInfo?.hasNextPage;67return (8 <div className="w-full h-full ">9 {!query.isFetchingNextPage && hasMore? (10 <button11 className="m-2 hover:text-purple-400 shadow-lg hover:shadow-purple"12 onClick={() => {13 query.fetchNextPage();14 }}15 >16 --- load more ---17 </button>18 ) : null}19 {query.isFetchingNextPage ? (20 <div className="w-full flex-center m-1 p-1">loading more...</div>21 ) : null}22 </div>23)24
by default react query will return the data as a pages arra that will add a new array into the pages array and that will require us to use nested loops to output the data
1 <div className="h-fit w-full flex-center flex-wrap">2 {pages?.map((page) => {3 return page?.user?.followers?.edges?.map((item) => {4 return (5 <div className='w-[30%] h-14 p-3 m-2 border border-green-500 '>6 user :{item.node.login}</div>7 );8 });9 })}10 </div>11
Also note the side effect functions provided by react query to handle the pagination getNextPageParam and getPreviousPageParam injects the value in this case the end cursor that the api gives us inside the pageInfo fields into the query function passed in.
in this case we'll change
1const fetchData = async () => await graphQLClient.request(USER,{name:"tigawanna",2first:10,after:null3});4
into
1const fetchData = async (deps: any) => {2 const after = deps?.pageParam ? deps.pageParam : null;3 return await graphQLClient.request(USER, {4 name: "tigawanna",5 first: 10,6 after,7 });8 };9
And we'll grab it from deps which will be receiving its value from
1 getNextPageParam: (lastPage: Page) => {2 return lastPage?.user?.followers?.pageInfo?.endCursor ?? null;3 },4
this works fine but if you want to filter for a certain keyword things get tricky so i made a function that transforms the data into a single array and filters any keywords provided
1export const concatFollowerPages = (data: RootResponse, keyword: string) => {23let totalRepos = data.pages[0].user.followers.edges;4 let i = 1;5 for (i = 1; i < data.pages.length; i++) {6 if (data?.pages) {7 totalRepos = [...totalRepos, ...data.pages[i].user.followers.edges];8 }9 }1011 const filtered = totalRepos.filter((item) =>12 item.node.login.toLowerCase().includes(keyword.toLowerCase())13 );14 const base = data.pages[data.pages.length -1].user;15 const user = {16 ...base,17 login: base.login,18 followers: {19 edges: filtered,20 totalCount: base.followers.totalCount,21 pageInfo: base.followers.pageInfo,22 },23 };2425 const final:RootResponse = {26 pageParams: [...data.pageParams],27 pages: [{ user: user }],28 };2930 return final;31};32
then we'll call it inside select
1const query = useInfiniteQuery(key, fetchData, {2 enabled: username.length > 3,3 select:(data:RootResponse)=>{4 return concatFollowerPages(data,"")5 },6 onSuccess: (data: RootResponse) => {7 // console.log("success", data);8 },9 onError: (error: any) => {10 console.log("error = ", error.response);11 if (error?.response?.status === "401" || error?.response?.status === "402") {12 // invalidate my locally stored token to send me back to13 // login screen,those codes mean tokenhas an issue14 }15 },16 getPreviousPageParam: (firstPage: Page) => {17 return firstPage?.user?.followers?.pageInfo?.startCursor ?? null;18 },19 getNextPageParam: (lastPage: Page) => {20 return lastPage?.user?.followers?.pageInfo?.endCursor ?? null;21 },22});
For the full example code i squeezed everything into a self contained component to make it easier to make sense of
{% embed https://gist.github.com/tigawanna/32acc9552e02634609184dc14f2076a3 %}