React-apollo caching of paginated queries (list of items) - reactjs

I have a paginated query that returns a list of items...
query snippet:
const MY_POTENTIAL_VOTERS = gql`
query potentialVoters(...) {
potentialVoters(...)
{
items {
id
....
}
pageInfo {
nextCursor
totalCount
}
}
}
`;
In react I then map over the data.potentialVoters.items and render them to UI. The UI then allows an action that will perform a mutation against one of the items in the page.
Mutation snippet:
const ASSOCIATE_PV_VOTER = gql`
mutation updatePotentialVoter($pv_id: String!, $voter_id: String) {
updatePotentialVoter(id: $pv_id, data: { state_file_id: $voter_id }) {
id
...
}
}
`;
When exploring the devtools it appears the cache for the affected item is updating correctly with the mutated info, however that row UI (from the page query) does not update as I might expect. Even with using refetchQueries option even them sometimes the mutation is not reflected in the list render, unless I hard refresh.
I have figured out one work around to my problem but I suspect it may not be a good idea. If my page query passes down just the ID of the item to a separate component which in turns uses it’s own query to get the data for that item, I think it is making smart use of the cache and everything updates as expected, but that seems like an anti-pattern that may lead to double fetching.
Might I be doing something wrong with the way I am performing the paginated query that does not allow a react update if a list member item is updated in cache? Is my workaround a really bad idea?

I refactored the page query from using Apollo HOC to the new Query component and that seems to fix everything. I was able to then use the update prop of the Mutation component to overwrite the mutatated object in the array of page items, which now causes my page to update as expected.

Related

Minimizing API calls using GraphQL and React

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/

Running many queries with react-query

I have a table that has the data of a different fetch per row. Each row will also be able to trigger a fetch independently of the other rows. If each row is a component that has a button to refetch, the implementation is easy. I just put call useQuery per row (a component).
The problem is filtering and sorting, because the fetched data is only on the rows so there is no global list containing all the information.
I tried to implement it with useQueries or just use components. But I only come up with gnarly solutions. One of those would be to have row components that call useQuery and also set a value (useState) on a parent. This looks like I'm setting the same data at 2 levels and if I get a big table that virtualizes rows, the useQuery inside the components are not triggered because the component is not created.
The problem is hard to describe, so if there is some part that needs clarification please let me know.
===
This is not the real code, just code to try to represent what I have:
function Row({cell}) {
const [fetch, setFetch] = useState(null);
const query = useQuery(["somekey", refetch], fetchFn(cell.url))
const refetch = () =>setFetch(Date.now())
return (<div onClick={refetch}>{query.data.value}</div)
}
function Table({array}) {
return (<div>
{array.map(el => <Row cell={el}/>})}
</div>)
}
This is generally not the easiest to implement with react-query, because it doesn't have a normalized cache, but here is how I would approach the problem:
have one list query: useQuery('myList')
each row has it's own query: useQuery('myList', id)
I would use initialData and staleTime to pre-populate the detail query with data from the list query and avoid unnecessary fetches when each row mounts.
when you refetch a single row, you:
refetch the one detail query, as you said, easy
onSuccess update the query data of the list with the new data from that detail query (with queryClient.setQueryData)
of course, all of this would be easier if you just had one list query and operate everything on that, but than you can't do individual refetches of one row - you'd always refetch the whole list. Usually, that's not too bad either. With the above approach, you get a bit into "syncing" state between the detail and the list query - also not so nice.

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.

GraphQL query returns undefined but works fine when I refresh the page?

I am executing a GraphQL query which is fairly complex. Sometimes it returns undefined, but when I refresh the page it works.
I'm building a web application using ReactJS for the frontend, and GraphQL to fetch the data. The query is quite complicated and even when I comment out the entire code in the page (so no information is actually rendered to the page, just they query is executed) , it still sometimes returns undefined.
I've tried the Query in the GraphQL playground and it works fine.
If I look at my server response, the 0.chunk.js.map response time is very long.
Does anyone know what this is likely to be?
The page I am rendering executed 5 different graphQL queries at different levels in the component hierarchy. But it is always the queries in the highest level component that frequently returns undefined.
data may be undefined while the query is being fetched from the server. You can provide a default value for it so that the destructuring will work regardless:
({ data: { movie } = {}, error, loading }) => {
You can even access more nested fields using destructuring, but you need to provide a default value at each level. For example:
({ data: { movie: { title } = {} } = {}, error, loading }) => {
Note that data could end up null (as opposed to undefined) if the request returns an error and it bubbles all the way up to the root. In this case, defaults won't help. But you can still guard against nulls this way:
const movie = data && data.movie
or use something like lodash's get.

How to do Batch Mutations with Apollo Client

I try to use ApolloClient 2.1 with the new Mutation Component.
Simple use cases are working but now I have something more complex.
What I want to achieve is to query data and put them in a list, then sort this list (here via react-sortable-hoc) and once sorted, I want to update the new position for all elements in the list.
So the basis is something like this, which is working for simple Querying:
const query = gql`
{
items( order:{by:"position", direction:"desc"}) {
id
name
position
}
}`
const ItemView extends Component {
onSortEnd = ({ oldIndex, newIndex }) => {
console.log("Sort ended: ", oldIndex, newIndex);
}
render() {
<Query query={query}>
{({ loading, data, error }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return (
<ItemList items={data.items} onSortEnd={this.onSortEnd} />
)
}}
</Query>
}
}
Now I really struggle at a lot of parts in order to do the mutation.
I think I need to wrap the Mutation Component. But how can I provide a GraphQL Query in there, because I want to do batch mutation with a similar query being fired multiple times, such as
mutation {
updateItem1: updateItem(id: 457092155, input: {position: 1}) {
item {
id
}
ok
errors
},
updateItem2: updateItem(id: 54489270, input: {position: 2}) {
item {
id
}
ok
errors
},
... // much more mutations, one for each item in the list
}
So my main question is, how do I pass a GraphQL mutation with dynamic amount of mutations to the Mutation component? Or should I do this completely differently?
Thanks a lot for any hints
You will have to compose multiple Mutations together in order to achieve this. You can use react-adopt for this. They have even addressed this here https://github.com/pedronauck/react-adopt#leading-with-multiple-params.
You can also take a look at the discussion going on here https://github.com/apollographql/react-apollo/issues/1867 and jasonpaulos has an example demonstrating this with hooks
Hi everyone! I believe that the new Apollo hooks, useQuery,
useMutation, and useSubscription, adequately address this use case. To
demonstrate this, I have converted #Cridda's example that uses
react-adopt and modified it to use #apollo/react-hooks here:
https://codesandbox.io/s/apollo-and-react-hooks-4vril
This example is by no means perfect, but it serves as a demonstration
of how hooks can massively simplify some use cases.
Hope this helps!
As Hemant mentioned already, the #compose annotation in Apollo 2.1 is the "correct" / conventional way to solve this problem. If that doesn't work for you for whatever reason, there is possibly another cruder/hacky way to accomplish this:
If your Item model has a parent model, you can mutate multiple nodes with one mutation by passing the children in as the array values to the connect / create / update actions.
The unfortunate limitation here is that there is no way to individually identify child nodes to be updated. What I mean is that you can filter child Items to be mutated based on a criteria (like postition = 2) but that will only allow you to mutate the filtered items to the same state; you won't be able to update them differently from one another this way.
If we allow ourselves one more crude step, you can delete the Item nodes that you wish to update before calling the update mutation - this will allow you to call the mutation with all of the updated items under the create: key in the mutation, which will allow you to specify each item to be created. In this way, the number of items you can create is only limited by the size of your request payload.
There are many cases where deleting and creating nodes is unacceptable (as opposed to updating them)...if you use this method then be sure there are no negative side effects to your use case(s) from deleting item data in this way.

Resources