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'
});
Related
I don't want my components to always refetch when they will mount and already have the query in the cache, so I did:
export const App = ({ props }) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnMount: false,
},
},
})
return (<QueryClientProvider client={queryClient}>{...restOfMyApp}</QueryClientProvider>)
}
When I save some config I tried to invalidate my query with the first key and with that queryClient.invalidateQueries(). In the React-Query devtools, it did show that the query is invalidated, but it just keeps the same results and don't refetch the query.
How can I invalidate the query to be refetched, but don't have to launch the query everytime the component mounts?
Thank you for your help!
It sounds like there's a bug in your code; could you show how you're invalidating the cache, and your cache keys?
Using refetchInactive should definitely solve the issue for you (emphasis mine):
When set to true, queries that match the refetch predicate and are not being rendered via useQuery and friends will be both marked as invalid and also refetched in the background
Here's a quick demo on stackblitz.
const client = useQueryClient();
client.invalidateQueries(YOUR_CACHE_KEY, { refetchInactive: true });
if your query is active, it should refetch with invalidateQueries. If its not active, as others have mentioned, set refetchInactive: true.
How can I invalidate the query to be refetched, but don't have to launch the query everytime the component mounts?
Often, caching is better described with a time-based approach, so the better solution to turning off the flags would be to set a staleTime to define how long your data is going to be valid, rather than to define certain points where you want / do not want a refetch.
if you set the staleTime for your query to, say, 20 minutes, none of the refetch events (onMount, onWindowFocus, onReconnect) will trigger a refetch in that time frame. invalidating it manually will still refetch it.
This would also be in-line with how HTTP caching works (think: Cache-Control: max-age=60)
to represent: fetch once, then never again (unless garbage collected), set staleTime: Infinity.
We are developing APIs using Apollo GraphQL. We use the out of the box caching solution that Apollo provides (KeyValueCache using a Redis datastore).
When a request query arrives, why does ApolloServer check the cache first before it does anything else?
Is there any way to insert logic before the cache is touched? For example, we want to do some authentication and permissions checking before Apollo checks the cache.
(Yes, there are directives but we find Public/Private scope and maxAge insufficient for our needs.)
The code and explanation below flags a few different approaches for you to explore -- hopefully one will suit your needs (I am assuming you know you can control whether Apollo looks to cache first by fetchPolicy - although I discuss this briefly below). First, consider using a HOC that checks permissions and authentication prior to returning the passed Component. The permissions/auth data can be passed as props should the passed Component be rendered.
withUserData = Component => {
const { isValidated, userData } = checkAuthAndPermissions(); // Modify for your implementation
if (!isValidated) return null;
return <Component userData={userData} />
}
You can thereafter wrap any Component that needs to make the auth/permission check with the HOC, as shown below. As also shown below, Apollo provides the opportunity to skip the query altogether looking to props or other logic, if that is something you may consider. Finally, through the option prop, you have the ability to set the fetchPolicy, which could be dynamically based on a permission check or props. With this fetchPolicy you could avoid looking to cache if that is an objective.
const ComponentWithApollo = graphql(YOUR_QUERY, {
skip: props => { /* consider permissions/auth here, skip if needed */ },
options: props => {
const fetchPolicy = determineFetchPolicyFromAuthOrPermissions();
return { fetchPolicy };
},
props: ({ data }) => data
})(YourComponent);
withUserData(ComponentWithApollo);
I am trying to use react hooks to make a Table component that displays rows of data from an API based on a set of filters that the user can choose. I want to make a new call to fetch data whenever the user clicks an 'Apply Filters' button, not when the user makes changes to the filters.
I am using context to manage the 'filters' state and a 'lastFetched' state which tracks when the user last clicked the 'Apply Filters' button (as well as other states on the page). Updates to the context are made via the useReducer hook and its dispatch method (see here).
The data fetching occurs in a useEffect hook that reruns whenever the 'lastFetched' state changes. This appears to be working correctly; however, the effect references other values from the context (i.e. the filters) that are not included in the dependencies. I am aware of the exhaustive-deps eslint rule, and I am concerned that I am not handling the hook's dependencies correctly.
const Table = () => {
const [context, dispatch] = useTableContext(); // implemented with createContext and useReducer
const { filters, lastFetched } = context;
useEffect(() => {
if (!filters.run) {
return;
}
dispatch({ type: 'FETCH_DATA_BEGIN' });
const params = convertContextToParams(context); // this is lazy, but essentially just uses the the filters and some other state from the context
API.fetchData(params)
.then((data) => {
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data.results });
})
.catch((e) => {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: e.response.data.message });
});
return () => { ... some cleanup... };
}, [lastFetched]); // <== This is the part in question
return <...some jsx.../>
};
Again, this appears to be working, but according to the react docs, it seems I should be including all the values from the context used in the hook in the hook's dependencies in order to prevent stale references. This would cause the logic to break, since I don't want to fetch data whenever the filters change.
My question is: when the user clicks 'Apply Filters', updates context.lastFetched, and triggers the useEffect hook, will the hook be referencing stale filter state from the context? If so, why? Since the effect is rerun whenever the button is clicked, and all the state updates are done via a reducer, does the usual danger of referencing stale variables in a closure still apply?
Any guidance appreciated!
Note: I have thought about using useRef to prevent this issue, or perhaps devising some custom async middleware to fetch data on certain dispatches, but this is the solution I currently have.
I am not an expert but I would like to provide my takes. According to my understanding of how Context works, you will not get stale filter data with the current implementation. useReducer updates the state with a new object which will trigger Table to be re-render.
Also, Table component doesn't really care about filter data unless lastFetched is changed by a click event. If lastFetched is changed, all the Consumer of TableContext will be re-render again. You should not get stale filter data either.
I am using react-apollo and have been for quite some time. One thing that has already been a problem for me is the fact that refetch doesn't work when using a mutation This has been a know issue for as long as I have been using the app.
I have got round this by using the refetch prop that is available on a query.
<Query query={query} fetchPolicy={fetchPolicy} {...props}>
{({ loading, data, error, refetch }) => {
... pass down to mutation
</Query>
However I am now reading in the documentation that you recieve
an update method as part of a mutation and you should use this to update your application after a mutation.
Can you use the update function to update your UI's data and have it update after finishing a mutation? If you can, is this the standard way to do updates now?
*Using refetchQueries not working
As you can see in the image the console.info() displays that the data.status = "CREATED"; but the request coming back from the mutation directly is data.status = "PICKED"; PICKED is the correct and uptodate information in the DB.
In order of preference, your options are:
Do nothing. For regular updates to an individual node, as long as the mutation returns the mutated result, Apollo will update the cache automatically for you. When this fails to work as expected, it's usually because the query is missing the id (or _id) field. When an id field is not available, a custom dataIdFromObject function should be provided to the InMemoryCache constructor. Automatic cache updates also fail when people set the addTypename option to false.
Use update. The update function will run after your mutation completes, and lets you manipulate the cache directly. This is necessary if the mutation affects a field returning a list of nodes. Unlike simple updates, Apollo has no way to infer whether the list should be updated (and how) following the mutation, so we have to directly update the cache ourselves. This is typically necessary following create and delete mutations, but may also be needed after an update mutation if the updated node should be added or removed to some field that returns a list. The docs go into a good deal of detail explaining how to do this.
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}}
>
{(addTodo) =>(...)}
</Mutation>
Use refetchQueries. Instead of updating the cache, you may also provide a refetchQueries function, which should return an array of objects representing the queries to refetch. This is generally less desirable than using update since it requires one or more additional calls to the server. However, it may be necessary if the mutation does not return enough information to correctly update the cache manually. NOTE: The returned array may also be an array of strings representing operation names, though this is not well documented.
<Mutation
mutation={ADD_TODO}
refetchQueries={() => [
{ query: TODOS_QUERY, variables: { foo: 'BAR' } },
]}
>
{(addTodo) =>(...)}
</Mutation>
Use refetch. As you already showed in your question, it's possible to use the refetch function provided by a Query component inside your Mutation component to refetch that specific query. This is fine if your Mutation component is already nested inside the Query component, but generally using refetchQueries will be a cleaner solution, particularly if multiple queries need to be refetched.
Use updateQueries. This is a legacy option that's no longer well-documented, but provided similar functionality to update before update was added. It should not be used as it may be deprecated in the future.
UPDATE:
You may also set up your schema in such a way that queries can be refetched as part of your mutation. See this article for more details.
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.