How to do Batch Mutations with Apollo Client - reactjs

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.

Related

React Query selectors

I am using react-query in my project and it works great
I wonder if react-query has a selector feature. I use react-query to fetch a list of posts, for example, and would like to filter only the completed post. I want to cache the filter result so the next time I filter the list again, it can return the cache. Basically, the feature I want is the same as selectors in recoil
react-query added first-class support for selectors with its release of version 3.
Here's example usage from the library:
it('should be able to select a part of the data with select', async () => {
const key = queryKey()
const states: UseQueryResult<string>[] = []
function Page() {
const state = useQuery(key, () => ({ name: 'test' }), {
select: data => data.name,
})
states.push(state)
return null
}
renderWithClient(queryClient, <Page />)
await sleep(10)
expect(states.length).toBe(2)
expect(states[0]).toMatchObject({ data: undefined })
expect(states[1]).toMatchObject({ data: 'test' })
})
It's important to have in mind that react-query does not provide a local state. All fetched data is a temporary representation (cache) of the server state. It implements the SWR (stale-while-revalidate) pattern.
Although you can achieve what you want by selecting the data from the cache provided by react-query(you need to do it yourself, there are no selectors) it might be tricky if you use pagination or if you data becomes stale immediately (default lib behaviour).
So assuming your data is a temporary representation of server state it would be better to include the filters in the query key so you would end up making different queries and having different independent cache entries to different filter combinations. This way:
You don't need selectors
You only fetch what you need
When you change the filters you have access to the cache data (if any) while react-query refetches in the background. This depends on how you configure react-query
I am not aware of a builtin native selector solution, but You can achieve this with a custom hook.
function useMostPopularPosts(){
const postsResult = useQuery(postsQueryParams)
const mostPopularResult = useQuery(popularPostsQueryParams)
if (!(postsResult.success && mostPopularResult.success)){
return [];
}
return postsResult.data.filter(
post=> mostPopularResult.data.includes(post.id)
)
}
function MostPopularPosts(){
const popularPosts = usePopularPosts()
return <>
{
popularPosts.map(post=>(
<PostHighlight post={post} />
))
}
</>
}
Yes, react-query has a selector feature.
(1) You can achieve this by passing your filters in the query key array (the filters can stand alone or be grouped in an object). Each query key will lead to different results, and they are cached default by 5 minutes. Next time when you filter the list again, it can return data from the cache without fetching API again.
(2) react-query also provided an option named select, but it just customs the returned data (filter or just return a part of the object, etc.) but not affects what gets stored in the query cache (full data).
You can still use this latter way, but the most suitable is the former mentioned.

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

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.

Correct way to share one query result throughout the app

Let's say at the top of the app, we retrieve some basic information about the app or user before rendering the rest of the application:
const getUser = gql`
query getUser(id: Int!) {
user(id: $id) {
id
name
}
}
`)
function App({ data }) {
return (
<div>
{!data.loading && !data.error && (
// the application
)}
</div>
)
}
export default graphql(getUser, {
options: (props) => ({ variables: { id: props.id }})
})(App)
Now anywhere in the application, it is safe to assume that the user has been loaded and is stored. What is the proper way for another deeply nested component to the retrieve the user data without having to redo the querying and loading logic?
This is the very basic use of a store-based library like Redux. This is not the purpose to guide every step of the way here but you are looking for a single source of truth as described here: http://redux.js.org/docs/introduction/ThreePrinciples.html
In short:
Receiving getUser response should trigger a 'LOGGED_IN' action dispatching user Data, this would be catched by a reducer updating the user object in your store (as much nested as you want), a container would then connect to this user in the store and have all its data using connect()
As of now, I'm not certain there is a proper way, but these are the options I think are reasonable
Manually pass down data via props
Wrap your deeply nested component with the same query
Manual pass down ensures your components rerender correctly, but it can be a pain to refactor. Wrapping your nested component would just hit the cache. Yes, you probably need to redo the loading logic, but that's not a show stopper.
My advice is to manually pass down props for shallow nested components and rewrap deeply nested components. Unfortunately, react-apollo doesn't provide a convenient way to access the apollo-store for nested components the same way that redux's connect container does.

React, avoid rerendering with shallowEqual

I'm starting to see performance problems and trying to optimize it.
As a first step, I'm dealing with Perf.printWasted()
That is I'm trying to eliminate unnecessary renders.
One of my component is being rerendered because of two props
a new date object.
newly created [todo] array
Suppose you are creating a calendar for todo.
For each date, I'm passing a date, and list of todos which are due that day.
I'm doing something like (simplified)
todoForDay = _.filter(todos, (todo) => {return todo.dueDate == today})
react's shallowEqual wouldn't see those two cases as equal, how should I proceed?
For #1, I could think of passing moment(date).format() as props and converting back to date object every time I pass the date.
But it would get really tiresome, because there are so many child components that needs access to the date.
Have you tried to implement the shouldComponentUpdate lifecycle method? You could check for the inequality of the passed in date prop and todos array like so:
class MyComponent extends Component {
shouldComponentUpdate(prevProps) {
const {
date,
todos,
} = this.props;
const {
date: prevDate,
todos: prevTodos,
} = prevProps;
return (
date.getTime() !== prevDate.getTime() ||
!_.isEqual(todos, prevTodos)
);
}
render() {
// render...
}
}
The _.isEqual method performs a deep equality comparison of the two todos arrays. There is also a _.isEqualWith method you could use to define your own notion of equality for those arrays if you want to be more specific.
Alternatively, you could look into something like Immutable.js as it would allow you to do an easier todos !== prevTodos comparison, but this might be overkill for your needs (depending on how much data you're working with).
If you're already doing something like this, perhaps provide some more code (your implemented shouldComponentUpdate method so we can suggest other alternatives).
For #1 you don't need to convert the prop. You can simply compare the dates with getTime() in shouldComponentUpdate():
shouldComponentUpdate(nextProps) {
return this.props.date.getTime() !== nextProps.date.getTime()
}
And for #2 unfortunately it looks like an array which contains objects, I think doing a deep equal here is more expensive than just a render.
Note that executing render() doesn't mean that the DOM will get an update. If you setup key properly then it should be fast enough. (If the todos may change its order or the newly added todo is added on top then don't use indexes as the key. Real unique keys are better in that case)
You should try to avoid unnecessary setState (if you are not using a state management library). Also try to split your components to small pieces. Instead of re-rendering a huge component every time it has an update, updating only the minimum sections of your app should be faster.
Another possibility is to re-structure your state. But it's based on your requirements. If you don't need the full datetime of each todo, you can group your state something like:
todos: {
'2017-04-28': ['Watch movie', 'Jogging'],
'2017-04-29': ['Buy milk']
}
By doing this you don't even need the filter. You can grab the todos of the date your want easily.
In a more complex case which you need more information, you can try to normalize your state, for example:
{
todos: {
1: { text: 'Watch movie', completed: true, addedTime: 1493476371925 },
2: { text: 'Jogging', completed: true, addedTime: xxxxxxxxxx},
3: { text: 'Buy milk', completed: false, addedTime: xxxxxxxxxx}
},
byDate: {
'2017-04-28': [1, 2],
'2017-04-29': [3]
}
}
Now if add a new todo to the todos, it won't affect your component which is referring to byDate so you can make sure that there is no unnecessary re-renders.
I'm sharing my solutions.
For calendar based todo list, I wanted to avoid implementing shouldComponentUpdate for every subcomponents for a calendar day.
So I looked for a way to cache the dates I created for the calendar. (Unless you change the month, you see the same range of dates) .
So https://github.com/reactjs/reselect was a great fit.
I solved #2 with the reselect as well.
It memoizes (caches) function result until function params change.

Observable Data Store Service with different Data Sets In Angular2

I'm trying to follow the "Observable data store" pattern in Angular 2 (detailed in this blog post from Angular University) From what I understand, this means that, if I have a service called TodoStore, I would subscribe to TodoStore.items$ so I could get all the latest updates for my to-do list in real time (as other components add, remove, or edit Todos.
However, what if I have two components side-by-side that display different Todolists, filtered on the server-side? One might display Todos due today while another would show Todos due on a user-selected date.
In this case, I would not be able to use the same TodoStore, as both would be fetching different data from the server. How do I handle sharing this service between these two components? My urge is to go back to an angular1-style getToDoList() service method, but then I'm storing state inside a single component and can't possibly share data between multiple components without making extra API calls.
If your lists really have to be filtered server-side and you have an unknown number of simultaneously displayed lists and a new server-request has to me made for each list + filter, then it is perfectly possible that using a single observable (TodoStore.items$) might not be a viable solution here and maybe some kind of getTodoList(forFilter) might be easier/quicker to implement.
Remeber: There is no such thing as "The perfect solution for all cases."
However: Even in this case you could use a store, which could something like this:
interface ITodoListDictionary {
[key: string]: Todo[];
}
#Injectable()
export class TodoStore {
todoLists$: BehaviorSubject<ITodoListDictionary> = new BehaviorSubject<ITodoListDictionary>({});
getListByKey(key: string): Observable<Todo[]> {
return this.todoLists$
.pluck(key)
.distinctUntilChanged() // optional, so you only get the update if there is an actually new list
.share();
}
// this would be called whenever the rest-response for a given list arrives
setListByKey(key: string, list: Todo[]): void {
this.todoLists$.take(1)
.do(dict => {
const newDict = Object.assign({}, dict, {[key]: list});
// if you are using TS2.1 you could also use:
// const newDict = {...dict, {[key]: list}};
this.todoLists$.next(newDict);
})
.subscribe();
}
}
...and in your template you could use it like this
<todo *ngFor="let todo of todoStore.getListByKey(someKey) | async" todo="todo"></todo>
Please keep in mind that is just one possible solution out of many - without seeing your actual application-flow it is hard to tell which might be the best solition.

Resources