Better way to fetch data and update from firebase with React - javascript

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.

Related

Update state if response is ok

So I have kind of a different problem here. My react app has a reducer that updates the state of a cart when a product is added. As i works right now i send this updated state to an API endpoint so save it to the database.
const addToCart = (item, quantity = 1) => {
dispatch({
type: 'ADD_ITEM',
payload: { item: item, quantity: quantity },
});
};
My api endpoint is called by a useEffect on this function.
useEffect(() => {
updateCart(state);
}, [removeFromCart, addToCart]);
So basically what I am doing is that I send the state to the db for removing and adding items to cart. But this comes with a problem. If a user adds many items to cart very rapidly the state and what is saved inn the database is out of sync.
Then I was thinking to revert the state if the response is not ok from the API endpoint. But a little unsure on how to do this. Anybody have any tips on how to solve this problem?
You should debounce the call to updateCart.
There's a lot of ways to implement a debounce on the useEffect. You can either use a library that already implements that hook or create a hook and manage the debounce yourself.
To get an overall ideia of what is debounce, you can look at this question.
To understand how it works with react and useEffect, you can look at this question
A simple way is just using useDebounce from react-use and do:
useDebounce(() => {
updateCart(state);
}, 1000, [removeFromCart, addToCart])
Where 1000 is the time for the debounce.
This way you remove the problem of having the user add and remove a lot of items and having the db out of sync and there's no need to check if the response is ok from the server.

Apollo optimisticResponse not applied when query is pending

If you have a pending query when a mutation with an optimisticResponse is executed, the optimisticResponse doesn’t get applied.
const {data, refetch} = useQuery(GET_TODOS);
const [updateTodo] = useMutation(UPDATE_TODO);
// in a form submit handler:
refetch();
// Immediately mutate while the _query_ is pending
updateTodo({
variables: { id, description: description + 1},
optimisticResponse: {
updateTodo: {
id,
__typename: "Todo",
description: description + 1
}
}
});
Minimal codesandbox.io example. There’s an artificial 1 second delay link added to make the effect more obvious.
The same behaviour appears to occur with direct cache writes as well; writes will not cause a re-render if there is a pending read query.
The same behaviour can also be witnessed if batching a query in with a mutation.
Is this the intended behaviour? And if so, is there a way to bypass it?
The Apollo useQuery hook uses a default fetch-policy of cache-first. Internally when the Apollo cache is updated the following occurs
Check if any queries are observing that part of the cache
Check if they should be notified of the update
Notify
When checking whether to notify a query, there is a check to see if the query is currently in flight to the server and if so only notify when the fetch-policy is cache-only or cache-and-network.
This is fine, and makes sense, you don't want to spend CPU re-rendering when you know the data is just about to update.
This causes a problem in the example above due to the refetch query being in progress when the optimistic update is applied. The shouldNotify check will return false. Changing the queries fetch policy fixes this
const {data, refetch} = useQuery(GET_TODOS, {
fetchPolicy: 'cache-and-network'
});

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.

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

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.

Only make API call when data in Vuex store is "stale" or not present

I am using Vuex for state management in my VueJS 2 application. In the mounted property of my component in question I dispatch an action...
mounted: function () {
this.$store.dispatch({
type: 'LOAD_LOCATION',
id: this.$route.params.id
});
}
...and this action uses axios to make an API call and get that location's details.
LOAD_LOCATION: function ({ commit }, { id }) {
axios.get(`/api/locations/${id}`).then((response) => {
commit('SET_LOCATION', { location: response.data })
}, err => {
console.log(err);
});
}
The mutation looks like so:
SET_LOCATION: (state, { location }) => {
state.locations.push(location);
}
This makes complete sense the first time this location is navigated to. However, let's say a user navigates to /locations/5 then navigates elsewhere in the app and returns to /locations/5 a few minutes later. Would it be a good idea to check for the location in state.locations and only make the API call if this location is not present? Or even better, to check the "staleness" of the location data and only make the API call to refresh the data after a certain period has passed?
Edit: Is there a pattern that is typically followed for these cases with Vuex? It seems to be a common case, but I'm not sure if jamming the logic to check for presence/staleness in the action is a solid approach.
Personally, I think it would be an excellent idea to do a check within the action to see if the data exists and set a timestamp when the data is received, then on subsequent calls it could determine if the data exits/is stale and act accordingly. That would speed up repeat visits and also save mobile users' data a bit.

Categories