How to update the UI after a delete mutation in react-apollo - javascript

I want the UI of my app to update after running a delete mutation in a react apollo component. The delete operation was successful but the UI did not update after the delete mutation. Below is a copy of my code, is there anything I am not getting right?
const deleteRoom = async (roomId, client = apolloClient) => {
const user = await getUserDetails();
const deleteResponse = await client.mutate({
mutation: DELETE_ROOM,
name: 'deleteRoom',
variables: {
roomId,
},
update: (cache, { data: roomDelete }) => {
const data = cache.readQuery({
query: GET_ROOMS_QUERY,
variables: {
location: user.location,
office: '',
page: 1,
perPage: 8,
},
});
data.allRooms.rooms = data.allRooms.rooms.filter(
room => room.id !== roomDelete.deleteRoom.room.id,
);
console.log(data, '======');
cache.writeQuery({
query: GET_ROOMS_QUERY,
data,
});
},
});
return deleteResponse;
};
I expected that the UI will be updated after executing the delete mutation, however, the UI doesn't get updated unless I do a force refresh.
N:B
When I console log the data, it actually removed the deleted data after filtering it out of the array. The updated data is what I am writing back to the cache

This behaviour is described in docs:
The difference between cache.writeQuery and client.writeQuery is that the client version also performs a broadcast after writing to the cache. This broadcast ensures your data is refreshed in the view layer after the client.writeQuery operation. If you only use cache.writeQuery, the changes may not be immediately reflected in the view layer. This behavior is sometimes useful in scenarios where you want to perform multiple cache writes without immediately updating the view layer.
You can use refetchQueries option in mutate to force refresh.

Make sure the object going into update as the "data" includes returning values and is not an empty object and thus will not update UI.
update: (cache, { data: roomDelete }) // log data
So the mutations can work but no UI changes at once without update passing data forward.
And cache.writeQuery works fine.

Related

How to use manual cache updates using RTK Query

I have recently started working on redux toolkit, Basically i want to refetch the data from database if there is mutation. I am using flatlist in my react native project where on pull to refresh i want my "getPosts" endpoint to refetch and get updated.
Note: I know about using tags for automated cache invalidation. but on web RTK query is not implemented.
Talking about pessimistic update:
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
try {
const { data: updatedPost } = await queryF`enter code here`ulfilled
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, updatedPost)
})
)
} catch {}
},
just give a help how to call this function from APP.JS or anywhere from the app. Thanks
You can just call refetch, you don't need anything that complicated. See the docs on useQuery
const myResult = useMyquery(args)
// in your pull to refresh trigger:
myResult.refetch()
If you are outside of a component, you can also still
store.dispatch(api.endpoints.myEndpoint.initiate(args, { track: false })))
to trigger a refetch

Using a value from a previous query in react query

I have a react query to get user data like this
const { data: queryInfo, status: queryInfoLoading } = useQuery('users', () =>
getUsers()),
);
I then have a sibling component that needs the same data from the get users query. Is there a way to get the results of the get users query without re-running the query?
Essentially, I would like to do something like this
const userResults = dataFromUserQuery
const { data: newInfo, status: newInfoLoading } = useQuery('newUserData', () =>
getNewUsers(userResults.name)),
)
As suggested in this related question (how can i access my queries from react-query?), writing a custom hook and reusing it wherever you need the data is the recommended approach.
Per default, react-query will trigger a background refetch when a new subscriber mounts to keep the data in the cache up-to-date. You can set a staleTime on the query to tell the library how long some data is considered fresh. In that time, the data will always come from the cache if it exists and no refreshes will be triggered.

Why does a reload return an empty state half of the time?

I'm creating a webshop for a hobby project in Nuxt 2.5. In the Vuex store I have a module with a state "currentCart". In here I store an object with an ID and an array of products. I get the cart from the backend with an ID, which is stored in a cookie (with js-cookie).
I use nuxtServerInit to get the cart from the backend. Then I store it in the state. Then in the component, I try to get the state and display the number of articles in the cart, if the cart is null, I display "0". This gives weird results. Half of the time it says correctly how many products there are, but the Vuex dev tools tells me the cart is null. The other half of the time it displays "0".
At first I had a middleware which fired an action in the store which set the cart. This didn't work consistently at all. Then I tried to set the store with nuxtServerInit, which actually worked right. Apparently I changed something, because today it gives the descibed problem. I can't find out why it produces this problem.
The nuxtServerInit:
nuxtServerInit ({ commit }, { req }) {
let cartCookie;
// Check if there's a cookie available
if(req.headers.cookie) {
cartCookie = req.headers.cookie
.split(";")
.find(c => c.trim().startsWith("Cart="));
// Check if there's a cookie for the cart
if(cartCookie)
cartCookie = cartCookie.split("=");
else
cartCookie = null;
}
// Check if the cart cookie is set
if(cartCookie) {
// Check if the cart cookie isn't empty
if(cartCookie[1] != 'undefined') {
let cartId = cartCookie[1];
// Get the cart from the backend
this.$axios.get(`${api}/${cartId}`)
.then((response) => {
let cart = response.data;
// Set the cart in the state
commit("cart/setCart", cart);
});
}
}
else {
// Clear the cart in the state
commit("cart/clearCart");
}
},
The mutation:
setCart(state, cart) {
state.currentCart = cart;
}
The getter:
currentCart(state) {
return state.currentCart;
}
In cart.vue:
if(this.$store.getters['cart/currentCart'])
return this.$store.getters['cart/currentCart'].products.length;
else
return 0;
The state object:
const state = () => ({
currentCart: null,
});
I put console.logs everywhere, to check where it goes wrong. The nuxtServerInit works, the commit "cart/setCart" fires and has the right content. In the getter, most of the time I get a null. If I reload the page quickly after another reload, I get the right cart in the getter and the component got the right count. The Vue dev tool says the currentCart state is null, even if the component displays the data I expect.
I changed the state object to "currentCart: {}" and now it works most of the time, but every 3/4 reloads it returns an empty object. So apparently the getter fires before the state is set, while the state is set by nuxtServerInit. Is that right? If so, why is that and how do I change it?
What is it I fail to understand? I'm totally confused.
So, you know that moment you typed out the problem to ask on Stackoverflow and after submitting you got some new ideas to try out? This was one of them.
I edited the question to tell when I changed the state object to an empty object, it sometimes returned an empty object. Then it hit me, the getter is sometimes firing before the nuxtServerInit. In the documentation it states:
Note: Asynchronous nuxtServerInit actions must return a Promise or leverage async/await to allow the nuxt server to wait on them.
I changed nuxtServerInit to this:
async nuxtServerInit ({ commit }, { req }) {
...
await this.$axios.get(`${api}/${cartId}`)
.then((response) => {
...
}
await commit("cart/clearCart");
So now Nuxt can wait for the results. The Dev Tools still show an empty state, but I think that is a bug, since I can use the store state perfectly fine in the rest of the app.
Make the server wait for results
Above is the answer boiled down to a statement.
I had this same problem as #Maurits but slightly different parameters. I'm not using nuxtServerInit(), but Nuxt's fetch hook. In any case, the idea is essentially: You need to make the server wait for the data grab to finish.
Here's code for my context; I think it's helpful for those using the Nuxt fetch hook. For fun, I added computed and mounted to help illustrate the fetch hook does not go in methods.
FAILS:
(I got blank pages on browser refresh)
computed: {
/* some stuff */
},
async fetch() {
this.myDataGrab()
.then( () => {
console.log("Got the data!")
})
},
mounted() {
/* some stuff */
}
WORKS:
I forgot to add await in front of the func call! Now the server will wait for this before completing and sending the page.
async fetch() {
await this.myDataGrab()
.then( () => {
console.log("Got the messages!")
})
},

Apollo-client local state cache race condition?

So I have been trying to use apollo-boost in a React app to use the cache to manage my client state using #client directives on queries but I have been having some issues.
Basically I'm using writeQuery() to write a boolean to my local app state in a Component (let's call it component A) and want to get that value in another Component (let's call it component B) using readQuery() inside the componentDidUpdate method. The thing is, readQuery() in Component B is running before writeQuery in Component A sets the value in the cache/local state so the value read by Component B comes out wrong.
I've confirmed this by using setTimeout to delay the readQuery() and indeed after using the timeout, the value is correct, but this solution can't be trusted, I'm probably not aware of something in Apollo Client because this functionality is pretty basic for local state management. Any Tips?
I believe that in Redux this is solved because the state is being injected to props, which makes the component update, so being that Component A is the one that changes the state, component B wouldn't even have to use componentDidUpdate to get the new value, since the state would be injected and Component B would get updated with the correct value.
Any help would be appreciated, sorry if I didn't make myself clear!
EDIT: The writeQuery() is being used inside a Mutation resolver.
Methods like readQuery and writeQuery are meant to be used to read and modify the cache inside of mutations. In general, they should not be used inside of components directly. By calling readQuery, you are only fetching the data from the cache once. Instead, you should utilize a Query component.
const TODO_QUERY = gql`
query GetTodos {
todos #client {
id
text
completed
}
}
`
<Query query={TODO_QUERY}>
{({ data }) => {
if (data.todos) return <ToDoListComponent todos={data.todos}/>
return null
}}
</Query>
The Query component subscribes to relevant changes to the cache, so the value of data will update when your cache does.
Similarly, you should create appropriate mutations for whatever changes to the cache you're going to make, and then utilize a Mutation component to actually mutate the cache.
const client = new ApolloClient({
clientState: {
defaults: {
todos: []
},
resolvers: {
Mutation: {
addTodo: (_, { text }, { cache }) => {
const previous = cache.readQuery({ query: TODO_QUERY })
const newTodo = { id: nextTodoId++, text, completed: false, __typename: 'TodoItem' }
const data = {
todos: previous.todos.concat([newTodo]),
}
cache.writeQuery({ query, data })
return newTodo
},
},
}
}
})
<Mutation mutation={ADD_TODO}>
{(addTodo) => (
// use addTodo to mutate the cache asynchronously
)}
</Mutation>
Please review the docs for more details.

Better way to fetch data and update from firebase with React

I have a World Cup Betting App. It has 64 matches, each one with your away_score and home_score, like this:
matches: {
1: {
name: 1,
type: "group",
home_team: 1,
away_team: 2,
home_result: ' ',
away_result: ' ',
date: "2018-06-14T18:00:00+03:00",
stadium: 1,
channels: [],
finished: false,
group: "a"
},
2: {...
I have an entity called MatchesBuilder, that fetch the data from the firebase and set the state. This is state is passed as props for a entity called GroupsBuilder, who has a List of Matches. Every time a user updates his bet for the match, it updates the values on the firebase.
fetchMatches = async () => {
const { firebaseApp, user } = this.props;
await firebaseApp
.database()
.ref(`/pools/${this.props.pool.key}/users/${user.uid}/matches`)
.once("value")
.then(snapshot => {
this.setState({
matches: this.snapshotToArray(snapshot),
isLoading: false
});
});
await this.checkBettingStatus();
};
And on my Match entity I have:
<TextField id={`${this.props.game.home_team}_home`}
type="number"
disabled={this.props.finishedTimeToBet}
value={this.props.game.home_result}
onChange={(e, game, type) =>
this.props.handleChangedResult(e, this.props.game, "home")
}
/>
It is working. But just because, I have a method that updates the state on the MatchesBuilder (it reloads all the data every time a user changes a value on the input) and pass as props again.
So everytime I update something on my Textfield, it calls a function and fetches again the data from firebase. Of course Im having performance issues.
What is your suggestion for a better and with better performance for this problem?
I'm not sure I got what you are saying correctly, but what about using 'on' instead of 'once'? That way the state should change whenever that node in the database does.
What about creating a submit button so that you are only ever updating your app when a user saves a bet? That would bring down the network requests substantially. Without any idea of what your view looks like it's hard to say if this is suitable but I think the general goal is to trigger only one network request per edit as binding a network request to an onChange handler seems excessive.
I'm sure you're able to code it, simply wrap your textfield in a form, add a button and use the onSubmit event of your form to send the network request.

Categories