Minimizing API calls using GraphQL and React - reactjs

I’ve written an API in GraphQL (Apollo Server), and a separate application in React (Utilizing Apollo Client). The app itself is really straight forward and doesn't require many calls to the API at all, and because of GraphQL, I can get all of the data a user needs in 2 calls and the only time I need to refetch the data is after a mutation.
I have a signin and a signup mutation, then two more mutations for creating an updating the main object a user interacts with.
I'm fairly familiar with React and GraphQL, but I feel like there has to be a way to make one 'larger' call after the signin/signup mutation that fetches all the data user a needs, rather than making a call to each level of nesting based on the loading of a particular component.
Mutations
SignUp
SignIn
CreateShirt
UpdateShirt
Queries
GetShirts
GetDesigns
Ideally, I could utilize a query similar to
query GetUser {
user {
id
email
shirts {
id
style
design {
name
swatch
}
}
}
}
So.. I could return all of this information in the SignIn / SignUp mutations but then after the UpdateShirt mutation, I don't have a (named) query that I can force to refetch. So then I leaned towards just creating a GetUser query that I could refetch but I don't know where to call it from.. it isn't specific to a component necessarily, more to a status of authentication.
What is the most efficient way to query my API for this information? Ignoring the ability to make nested queries and make the components ask for it seems silly.

I think, this is what you are lookin for?
https://www.apollographql.com/docs/react/data/mutations/#refetching-queries
After calling the mutation, this way, you can re-fetch your queries.
// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
{query: GET_POST}, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});
However, if you want one request then that means what you are lookin for is Batching.
You can batch multiple queries, something like:
const { data } = useQuery(gql`
query ShameelQuery {
query1
query2
}
`)
You can find more details in their official docs:
https://www.apollographql.com/blog/apollo-client/performance/batching-client-graphql-queries/

Related

How to share data across multiple components with react-query useMutation

I'm looking for a solution to share data across a React-Query mutation without having to create my own internal state or context.
I created a custom hook that takes care of the API call.
myData.ts
const useDataMutation = () => useMutation('MY_DATA_MUTATION', postData);
Then, I use my custom hook in different components.
Component1 is in charge of mutating. The response data will be available in data once the mutate is successful.
Component1.tsx
const { mutate, data } = useDataMutation();
useEffect(() => mutate('some_data'), []);
In another nested component, I want to access the data which came back from the response. But I don't want to pass down the data to 3-4 layers of components. And I wanted to avoid using a Context to access this data.
What I want is something like this:
Component2.tsx
const { data } = useDataMutation();
console.log({ data }); // log data once available.
But in this example, the data from Component2.ts is always undefined.
Is there a simple way to achieve something like this?
Thank you.
at the moment, mutations don't share data across instances like queries do. There is an open issue about it, and contributions are welcome.
If you're using #apollo/client then you can read the previously fetched data directly from the cache.
import Query from './query.ts'
const { todo } = client.readQuery({
query: Query,
variables: {}
})
That will not fetch the data from your server again and instead fetch it from the apollo cache. So you can fetch it using the hook or in the parent component and then 5 levels down you can just pull it again from the cache using the same hook.
When you're using a mutation it will update data in the cache if the properties in the query is the same as before, so if you have a query for getUser and a mutation for updateUser I think the mutation should automatically update the getuser cache data if the data aligns with the mutation data. I'm not sure about this.

How to update a state by calling a query when you click a button using react, typescript and graphql? [duplicate]

I have a mutation (UploadTransaction) returning certain list of certain object named Transaction.
#import "TransactionFields.gql"
mutation UploadTransaction($files: [Upload!]!) {
uploadFile(files: $files){
transactions {
...TransactionFields
}
}
}
Transaction returned from backend (graphene) has id and typename field. Hence it should automatically update Transaction in the cache. In chrome dev tools for Apollo, I can see new transactions:
I also have a query GetTransactions fetching all Transaction objects.
#import "TransactionFields.gql"
query GetTransactions {
transactions {
...TransactionFields
}
}
However I don't see newly added Transaction being returned by the query. During initial load, Apollo client loaded 292 transactions which it shows under ROOT_QUERY. It keeps returning same 292 transactions. UploadTransaction mutation add new object of type "Transaction" in cache in dev-tools without affecting ROOT_QUERY in dev-tools or my query in code.
TransactionFields.gql is
fragment TransactionFields on Transaction {
id
timestamp
description
amount
category {
id
name
}
currency
}
Any idea what am I doing wrong? I am new to apollo client and graphql
From the docs:
If a mutation updates a single existing entity, Apollo Client can automatically update that entity's value in its cache when the mutation returns. To do so, the mutation must return the id of the modified entity, along with the values of the fields that were modified. Conveniently, mutations do this by default in Apollo Client...
If a mutation modifies multiple entities, or if it creates or deletes entities, the Apollo Client cache is not automatically updated to reflect the result of the mutation. To resolve this, your call to useMutation can include an update function.
If you have a query that returns a list of entities (for example, users) and then create or delete a user, Apollo has no way of knowing that the list should be updated to reflect your mutation. The reason for this is two fold
There's no way for Apollo to know what a mutation is actually doing. All it knows is what fields you are requesting and what arguments you are passing those fields. We might assume that a mutation that includes words like "insert" or "create" is inserting something on the backend but that's not a given.
There's no way to know that inserting, deleting or updating a user should update a particular query. Your query might be for all users with the name "Bob" -- if you create a user with the name "Susan", the query shouldn't be updated to reflect that addition. Similarly, if a mutation updates a user, the query might need to be updated to reflect the change. Whether it should or not ultimately boils down to business rules that only your server knows about.
So, in order to update the cache, you have two options:
Trigger a refetch of the relevant queries. You can do this by either passing a refetchQueries option to your useMutation hook, or by manually calling refetch on those queries. Since this requires one or more additional requests to your server, it's the slower and more expensive option but can be the right option when A) you don't want to inject a bunch of business logic into your client or B) the updates to the cache are complicated and extensive.
Provide an update function to your useMutation hook that tells Apollo how to update the cache based on the results of the mutation. This saves you from making any additional requests, but does mean you have to duplicate some business logic between your server and your client.
The example of using update from the docs:
update (cache, { data: { addTodo } }) {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}
Read the docs for additional details.

apollo-client used to make multiple requests to make a real-time search

We're developing a search engine inside an app with Apollo and we do not know exactly how to develop a real-time search engine that makes a request to the server on every keyboard press.
On the documentation it says that we must use the new <Query /> component, but I see that this case mostly fits with firing a manual query: https://www.apollographql.com/docs/react/essentials/queries.html#manual-query
I don't know if I'm correct, or maybe we should use it in another way.
thanks!
As it is said at a link you shared, if you wanted to delay firing your query until the user performs an action (your case), such as clicking on a button, you want to use an ApolloConsumer component and directly call client.query() instead.
Query component can't be used in this situation because when React mounts a Query component, Apollo Client automatically fires off your query during rendering.
UPDATE
With Apollo Client V2.6, it is now possible to make a Query to the server manually using a hook. The hook that you want is useLazyQuery.
You'd have something like this;
const [onSearch, { called, loading, data }] = useLazyQuery(
SEARCH_QUERY,
{ variables: { searchText: state.searchText } }
);
Then you can call the onSearch function whenever your text changes inside a useEffect like below.
useEffect(() => {
onSearch()
}, [state.searchText])
Note that you might want to dounce your onSearch function such that you don't hammer your server on every key stroke.

Synchronously read from the store?

So I'm using Apollo for my app's state, and am so far a little taken aback there's no equivalent to mapStateToProps or something.
As far as I understand, to have any data globally accessible in my store, once I get the data, I need a query to write the data to the store, then another query in my other component to go and get it.
By this point, the other component has very much mounted and rendered, so content just sort of flickers in and out.
In Redux, I can just add new data to the store in my reducers, then anything that's connected with mapStateToProps has access to it.
Is there an equivalent? Or does everything need to go through asynchronous queries? Does anyone else kind of find this an enormous pain?
For example, in one component I'm getting some invitation data:
this.props.client.query({
query: REQUEST_ACTION_DATA,
variables: {
id: actionData.id,
type: actionData.type
}
}).then(data => {
this.props.client.writeQuery({query: GET_ACTION_DATA, data: {
action: {
type: data.data.actionData.type,
object: data.data.actionData.invitation,
__typename: 'ActionDataPayload'
}
}})
this.props.history.push('/auth/register')
})
... then in my other component I have this:
componentWillMount() {
const authToken = localStorage.getItem(AUTH_TOKEN);
if (authToken) {
this.props.history.push('/')
}else{
this.props.client.query({
query: GET_ACTION_DATA
}).then(data => {
if(data.data && data.data.action && data.data.action.type == 'invite'){
this.setState({
invitation: data.data.action.object
})
}
console.log(data)
})
}
}
ignoring the fact that it's hugely unwieldy to write all this for something so simple, is there just a way to access store data without having to wait around?
The graphql higher order component from react-apollo is a supercharged connect from redux. It will do the following:
When the component mounts it will try and execute the query on the Apollo cache. You can think of the query as a DSL for selecting data not only from the server but also from the store! If this works the component is pretty much instantly filled with the data from the store and no remote requests are executed. If the data is not available in the store it triggers a remote request to receive the data. This will then set the loading property to true.
The Apollo cache is a normalised store very similar to your redux store. But thanks to the well thought through limitations of GraphQL the normalisation can happen automatically and you don't have to bother with the structure of the store. This allows the programmer to forget about stores, selectors and requests altogether (unless you are heavily performance optimising your code). Welcome to the declarative beauty of GraphQL frontend frameworks! You declare the data you want and how it gets there is fully transparent.
export default graphql(gql`{ helloWorld }`)(function HelloWorld({ data }) {
if (data.loading) {
return <div>Loading the simple query, please stand by</div>;
}
return <div>Result is: {data.helloWorld}</div>;
});
No selector, no denormalisation happening. This is done by Apollo Client! You will get updated data when the cache updates automatically similar to Redux.
No componentWillMount checks for data is in store already and triggering request actions. Apollo will make the checks and trigger queries.
No normalisation of response data (also done by Apollo Client for you)
If you do any of the above you are probably using Apollo Client wrongly. When you want to optimise your queries start here.

React GraphQL Apollo - how to fetch multiple times at once

I have a component which fetches data with one query with a parameter. I'd like to make this component call this query twice, with different parameters based on props (so maybe later even more-times). How can I achieve this? This is example of my code:
...
const getUser = gql`
query getUser($last_name: String!, $first_name: String!) {
user(last_name: $last_name, first_name: $first_name) {
id
last_name
first_name
}
}
`
export default graphql(
getUser, {
options: (props)=>{
return {
variables: {
last_name: ...something from props,
first_name: ...something from props
}
}
}
})(ComponentClassName)
Usually I fetch the appropriate user based on the props. That's ok. But now, I'd like to use this component and be able to fetch more users, without changing backend and much frontend. At least I need to be able to define through props which users to fetch, and if one or more. How can I do this? Thanks.
It sounds like you're using this component (let's call it User) as part of another one that has a list of users (let's call it UserList).
I see two solutions here:
If the UserList queries the list of users and gets their username which is later passed as a prop to each one of the User components and you can still use your getUser query. For performance reasons, and since you are using Apollo, it's probably a good idea to enable batching (https://dev-blog.apollodata.com/query-batching-in-apollo-63acfd859862), as you'll make one query per user.
You get all the data needed for each individual User in UserList and then pass it down as props. No queries would be made within each User component.
Hope that helps!
I think what you're looking for here is compose. With it, you could perform multiple graphql operations on one component. I implemented one here, which i composed both a query and a mutation.
You would be able to use it to compose two queries together, since they both have different props as mentioned.

Resources