React GraphQL query results as an object without JSX? - reactjs

I am coming here as a last resort! I have been searching for 1-2 weeks now and haven’t came across anything!
I want to use GraphQL in react (I have an Apollo react client and Apollo backend), but I want to be able to call a query and return the results as an object without it needing to be done using a component or class.
I have a tried using client.query but whatever way I try I only get promises returned.
I want something like this:
const myData = some kind of query call
edit:
Want I want to do is pass an array of objects into a component which will then render the data how I want it.
An example would be passing a list of events into a calendar cell to be displayed for that day.
Here's a better example of what I want to do:
const CustomCellOne = () => (
<Query query={my_query} variables={ vars }>
{({ loading, error, data }) => {
const dataPoints = data.blahQuery;
return (
<CustomCellTwo data={dataPoints} />
)
}}
</Query>
);

Network requests are asynchronous to prevent blocking UI. The Fetch API, which Apollo uses under the hood, utilizes Promises instead of the more traditional callbacks to handle asynchronously requesting resources across the network, so Apollo's API is based on Promises as well. There is no method available that will allow you to synchronously fetch a GraphQL query -- you just need to structure your code appropriately to handle the Promise once it resolves.
The following examples assume you've correctly configured a client instance. Using then:
function doSomething () {
client.query({ query: SOME_QUERY })
.then(({ data }) => {
// Use the returned data however you like
})
.catch(error => {
// Handle error
})
}
Using async/await:
async function doSomething () {
try {
const { data } = await client.query({ query: SOME_QUERY })
} catch (error) {
// Handle error
}
}
If you are unfamiliar with Promises, it would be to your benefit to review the appropriate documentation and check out some tutorials.

Related

Fetching nested async data to fill a React component

Hoping to get some guidance on how to best work with async data calls in React.
I have a page that has a FullCalendar component which will show the events given to it in a state, i.e., simplifying a lot,
return (
...
<FullCalendar events={ events } />
)
What I need to do to get the events is first to make a call to Google Calendar's APIs to fetch the Oauthed user's calendars, then looping through each calendar, make a call to Google calendar to fetch that calendar's events. Something like (very simplified):
let eventsToShow = [];
apiCalendar.listCalendars({}).then(({result}) => {
// At this point I have the list of the user's calendars
result.items.forEach((calendar) => {
// Now I have all the events on a given calendar
apiCalendar.listEvents(calendar.id).then(({result}) => {
eventsToShow.push({id: result.id, start: result.start, end: result.end});
})
})
})
setEvents(eventsToShow);
Right now, I have a button on the page, and I'd like to kick off the calls to fetch the calendars and events when I click the button (because I have a separate button that does the Oauth sequence). Later, my intent is to store something from the Oauth so that the page will just pull the calendars and events automatically if the token is present.
In the above pseudocode, I have various pieces working - the FullCalendar component can show hard-coded events, my various API calls do fetch the correct date - but I'm having trouble figuring out how to put it all together.
I suspect I have to use a useEffect as well as use await, but the nested nature of the API calls is confusing me. If I do have to use useEffect, I'm also not sure how to get the calls to trigger off a button click for now.
I've tried having an onClick function for the button that just leads to essentially the second block of pseudocode above. This worked for causing the API calls to run (as evidenced by console.logs along the way), but the setEvents part at the end doesn't work, I suspect because of the async nature of things.
Help? Am I completely misguided?
If what you want is to bind the api calls to the click of the button you wouldn't need a useEffect.
Definitely you can improve the readability of the async calls with the use of async await. From what you shared I get that the async call should be something like this:
const fetchCalendarEvents = async()=>{
try{
const calendars = await apiCalendar.listCalendars()
const events = await Promise.allSettled(calendars.map(c=>{
return apiCalendar.listEvents(c.id)
}))
return events
}
catch (error){
console.error(error)
}
}
You can add the required parameters that you need to do the api call.
Within the component then you can define the onClick event on the button which will trigger this function something like
const handleEventsFetch = async()=>{
const events = await fetchCalendarEventts(
//...params
)
setEvents(events)
}
If you ever want the fetching to be on component load you can definitely do that in a useEffect. Note you cant use async-await in useEffect but we can use then/catch or an IIFE
//then
useEffect(()=>{
fetchCalendarEvents(
//...
).then(
setEvents
)
},[])
//iife
useEffect(()=>{
(async()=>{
const events = await fetchCalendarEvents(
//...
)
setEvents(events)
})()
},[])
Note I don't know what the params for the api calls are so beware that you might need to add dependencies to the useEffect. In consequence of that you might also need to add some if checks before running the api call so that you don't run it multiple times if any of the dependencies change
EDIT: Add transformation on returning the events
const fetchCalendarEvents = async () => {
try {
const calendars = await apiCalendar.listCalendars();
//rename this so that it is more descriptive
const calendarsWithEvents = await Promise.allSettled(
//make the map function async!
calendars.map(async (c) => {
// now you can use await
const calEvents = await apiCalendar(c.id);
return {
calendarId: c.id,
events: calEvents,
};
})
);
return calendarsWithEvents;
} catch (error) {
console.error(error);
}
};

How to retrieve Shopify's 'extensions' object in GraphQL response for query cost data

I have a React app that I have cloned from the shopify-app-node repo (the latest version using Express). I'm making GraphQL calls to Shopify's Admin API and I would like to also return cost information. I understand that there is an extensions object that contains this information but it is not returned in a typical GraphQL call. I have attempted to read and understand the documentation of Shopify/shopify-node-api but Shopify's current version of their React app doesn't reflect what the documentation states. Not in a way I can understand anyway.
This is the route defined when the server is initiated:
app.post("/graphql", verifyRequest(app), async (req, res) => {
try {
const response = await Shopify.Utils.graphqlProxy(req, res);
res.status(200).send(response.body);
} catch (error) {
res.status(500).send(error.message);
}
});
And then I make the query with the following: (apolloClient is being passed down from the parent component as this is a helper function and useQuery won't work due to it being a hook.
const apolloClient = useApolloClient();
apolloClient.query({
query: GqlProducts,
variables: {
count: 10
}
})
.then( response => {
console.log(response.data);
console.log(response.extensions);
})
.catch( error => console.log(error) )
Obviously I get an undefined on response.extensions.
Along with any hints or suggestions on how to get this working, I'm also curious to know why it's not returned in a typical response.

How to use { useQuery } from react-query while iterating over an array of data

I'm trying to integrate react-query into my React project.
What I have is a custom hook - useBundles which fetches data from a GraphQl endpoint as shown below -
function useBundles() {
const { data, status, isSuccess } = useQuery(
'SaleBundles',
async () => await request(endpoint, gql`query {${saleQuery}}`),
);
return { data, status, isSuccess };
}
I use the return value in my component like this const { data, status, isSuccess } = useBundles(); which works perfectly fine.
Now, what I want to do is, for each item in data I want to call another endpoint (a REST endpoint this time) and I have a seperate async function for that called getData(data: Array) which uses async-await to fetch data from my REST endpoint.
I could just call getData in useBundles passing to it data as an argument. But since getData is async it is required to use await with it (which I can't because I can't define a hook as async).
As an alternative, I tried not to use getData in useBundles but directly call the endpoint using axios like this -
data.forEach((item) => useQuery("some-unique-key", axios.get(<endpoint>)))
This gives me an error saying that useQuery cannot be used in a loop.
I'm kinda stuck at this point as how to proceed. Would appreciate any help. Thanks.
There are basically two ways to solve this:
useQueries
useQueries can be used with a fixed array, but also with a dynamic one, so:
useQueries(data.map(item => ...))
would work. It will give you an array of QueryResults back.
map and create a component
data.map(item => <Item {...item} />
then, you can call useQuery inside the Item component.
Both approaches will yield concurrent requests, the difference is mainly where you have access to the response data: With useQueries, you have access to all of them in your root component. With the second approach, each Item will only have access to its own data. So it just depends on your needs / use-case which version you prefer.

UseApolloClient query won't return fetchMore

I am working on project with Apollo on client side. I am using react-apollo-hooks on my client side. And I have a problem with useApolloClient.
When i fire query with my client I got in useApolloClient I don't get back all data I need. FetchMore is missing. If I use regular query (useQuery) I get that. But problem is I need to fire that query on click and i need to use one provided with apollo client.
I have this function for fetching data on click:
const bulkSearch = async data => {
setContent(<Spinner />);
showModal();
try {
const response = await client.query({
query: BULK_SEARCH_PRODUCTS,
variables: { data }
});
if (!response.loading) {
setContent(
<ProductsListDisplay
products={response.data.bulkSearch.products}
fetchMore={response.fetchMore}
count={{ total: 10 }}
/>
);
return 200;
}
} catch (err) {
return 400;
}
};
And response doesn't contain fetchMore.
On the other way classic query returns fetchMore.
const newdata = useQuery(BULK_SEARCH_PRODUCTS, {
variables: { data: { ids: ["536003", "513010"] } }
});
Some help ? Thank you!
According to the apollo-client docs, ApolloClient.query returns a Promise that resolves to an ApolloQueryResult, which is a simpler object that has only data, errors, loading, networkStatus, and stale as properties.
On the other hand, the render prop argument of react-apollo's Query component gets fed a much richer object, with fetchMore being one of its additional properties. If you want to do something similar using the raw ApolloClient object, you would have to use ApolloClient.watchQuery, which returns an ObservableQuery that you can subscribe to consume results. The benefit of this is that you have access to more methods, such as ObservableQuery.fetchMore.
Note this approach is fundamentally different than using ApolloClient.query, since that function requests one query and returns the result as a Promise, while ApolloClient.watchQuery consistently monitors your cache and pushes updated results to your subscribe method when the cache store changes, so it's a more complicated lifecycle. In general, if you're already using react-apollo or one of the #apollo/react-X packages, you probably want to stay away from ApolloClient.watchQuery, since the functionality from those libraries builds directly on top of it and is designed to be easier to consume.
Hope this helps!
You have to create your own FetchMore method for this. This has to be handled by you that's the safer you to go.
In my case I needed
fetchMore
Adding Infinite loading and should event deal with loading state as well.
Problem with default loading state is that it will be always false as return of promise.
When you use await client.query.
In our query we have cursor based pagination.
read this
Create Function that will trigger on scroll ( end of page )
Check on value of after and update it with state management
Loading as well as data also needs to be in state.
Code:
const fetchMoreFilteredData = async (after) => {
try {
setFilteredLoading(true); // set this state in order to show loading indicator
const { data, loading } = await client.query({
query: QUERY,
variables: {
after: after,
...all variables,
},
fetchPolicy: "network-only",
notifyOnNetworkStatusChange: true,
});
const {
query: {
pageInfo: { hasNextPage, endCursor },
},
} = data;
setFilteredData({
// update your data ...filteredData,
});
setHasNextPage(hasNextPage); // check if there is next page
setEndCursor(endCursor); // set end cursor for next page this will guide the query to fetch next page
setFilteredLoading(loading); // set loading state to false
} catch (error) {
error.graphQLErrors.map((error) => {
console.log("error", error.message);
});
setFilteredLoading(false);
} };
const handleLoadMore = () => {
hasNextPage && fetchMoreFilteredData(_endCursor);
};

How to use helper functions with Apollo GraphQL

I find myself in situations with React/Redux apps where I require a lot of functions to transform data when it comes back from the server by using libaries like moment or jwtDecode. For example:
function mapDispatchToProps(dispatch) {
return {
getAllTokens: () => {
dispatch(getAllTokens());
}
};
}
On page load, I run this.props.getAllTokens() to bring back all tokens from the server stored in the database which I'd like to show on the page (for example).
The dispatch uses Redux Thunk to perform some operations on the data and send to the store:
export const getAllTokens = () => dispatch => {
apiGetAllUsers()
.then(tokens => {
let newFormat = tokens.message.map(token => {
if (token.refreshToken) {
let decoded = jwtDecode(token.refreshToken);
let expiresIn = moment.unix(decoded.exp).fromNow();
return {
refreshToken: token.refreshToken,
email: token.email,
expiresIn
};
}
});
dispatch(adminTokensReceived(newFormat));
})
};
This function getAllTokens is in another file to help the component keep lean. This approach works fine for Redux, but when it comes to using Apollo instead, how would I update the data before adding it back to the component props?
I have came across this link which shows how to update data from a query, but I don't see many examples using it, so I wondered if I'm missing something fundamental?
So my questions is, what is the best way to use helper functions as I've described above?
Thanks

Resources