T
TanStack17mo ago
fascinating-indigo

Using queryClient as a useCallback dependency? Possibly dangerous?

Hi all. Chasing a very frustrating bug in production. We noticed occasionally our users would submit 20k requests in under a minute, effectively DoS'ing us. We're adding throttling etc, but I want to track down WHY they are sending so many requests. We think it's due to invalidation of some query keys, causing refetches to happen. I cam across this code:
const onUpload = React.useCallback(() => {
queryClient.invalidateQueries(
getParticipantBaseKey({
organizationId: organization.id,
participantId,
}),
);
hide();
}, [organization.id, participantId, queryClient, hide]);
const onUpload = React.useCallback(() => {
queryClient.invalidateQueries(
getParticipantBaseKey({
organizationId: organization.id,
participantId,
}),
);
hide();
}, [organization.id, participantId, queryClient, hide]);
We call this when a file is finished uploading. Note the queryClient in the dep array? Is it possible this is changing, and causing the invalidateQueries call to run many times? We will destructure and use just the invalidateQueries fn as a dep, as it's good practice, but I want to know if this is the cause/ Does anyone have any ideas?
15 Replies
rival-black
rival-black17mo ago
sounds like ure on the right track
fascinating-indigo
fascinating-indigoOP17mo ago
Perhaps. But destructuring the query client like that is not supported as it’s a class
rival-black
rival-black17mo ago
queryclient as a dep is quite heavy 😅 ruins the point iwth use callback
const { invalidateQueries } = queryClient;

const onUpload = React.useCallback(() => {
invalidateQueries(
getParticipantBaseKey({
organizationId: organization.id,
participantId,
}),
);
hide();
}, [organization.id, participantId, invalidateQueries, hide]);
const { invalidateQueries } = queryClient;

const onUpload = React.useCallback(() => {
invalidateQueries(
getParticipantBaseKey({
organizationId: organization.id,
participantId,
}),
);
hide();
}, [organization.id, participantId, invalidateQueries, hide]);
🤔 ah jeez
fascinating-indigo
fascinating-indigoOP17mo ago
Yeah. The docs suggest it should be stable. Yeah invalidateQueries will not have correct reference to this, so updating the query cache internally fails There’s an issue on github So unsure. Perhaps the bug I have is unrelated entirely
rival-black
rival-black17mo ago
cc @TkDodo 🔮 maybe ideas that come to mind - memoize query client call - use ref for the query client to ensure stable across re renders const queryClientRef = React.useRef(queryClient); im new to react query so idk if using a ref would be ok
fascinating-indigo
fascinating-indigoOP17mo ago
Yeah. I think before I try those things, I need a way to know when it occurs and log or report the case, so I can know if the fix works. Right now it occurs rarely, perhaps twice a day judging by our load balancer logs. Without a clear reproduction, or something to confirm the occurence (and thus the fix actually worked) I’ll never be satisfied the issue is gone Wonder if I can track invalidate calls in a specific time period and report that out As I suspect I am calling it hundreds of times erroneously
rival-black
rival-black17mo ago
use a logging tool :ThinkingIsHard:
const onUpload = React.useCallback(() => {
const queryKey = getParticipantBaseKey({
organizationId: organization.id,
participantId,
});

track(queryKey);

queryClient.invalidateQueries(queryKey);
hide();
}, [organization.id, participantId, hide]);
const onUpload = React.useCallback(() => {
const queryKey = getParticipantBaseKey({
organizationId: organization.id,
participantId,
});

track(queryKey);

queryClient.invalidateQueries(queryKey);
hide();
}, [organization.id, participantId, hide]);
a custom tracking tool may look something like
const invalidateQueriesTracker = {
calls: [],
timeThreshold: 60000, // 1 minute
limit: 100, // Maximum allowed calls within the time threshold

track: function(queryKey) {
const timestamp = Date.now();
this.calls.push(timestamp);
this.checkAndReport();
},

checkAndReport: function() {
const now = Date.now();
const startTime = now - this.timeThreshold;
const callsWithinThreshold = this.calls.filter(timestamp => timestamp >= startTime);

if (callsWithinThreshold.length > this.limit) {
// Trigger reporting mechanism
console.warn(`Excessive invalidateQueries calls detected: ${callsWithinThreshold.length} calls within ${this.timeThreshold}ms`);
// Add your desired reporting logic here (e.g., send an alert, log the incident)
}

// Clean up old timestamps
this.calls = callsWithinThreshold;
}
};
const invalidateQueriesTracker = {
calls: [],
timeThreshold: 60000, // 1 minute
limit: 100, // Maximum allowed calls within the time threshold

track: function(queryKey) {
const timestamp = Date.now();
this.calls.push(timestamp);
this.checkAndReport();
},

checkAndReport: function() {
const now = Date.now();
const startTime = now - this.timeThreshold;
const callsWithinThreshold = this.calls.filter(timestamp => timestamp >= startTime);

if (callsWithinThreshold.length > this.limit) {
// Trigger reporting mechanism
console.warn(`Excessive invalidateQueries calls detected: ${callsWithinThreshold.length} calls within ${this.timeThreshold}ms`);
// Add your desired reporting logic here (e.g., send an alert, log the incident)
}

// Clean up old timestamps
this.calls = callsWithinThreshold;
}
};
although u prolly wanna use a logging tool for this winston axiom sentry someth around these
fascinating-indigo
fascinating-indigoOP17mo ago
Perfect thank you
foreign-sapphire
foreign-sapphire17mo ago
queryClient is stable because it's a class you just create once. Can safely be added as a dependency.
foreign-sapphire
foreign-sapphire17mo ago
harsh-harlequin
harsh-harlequin17mo ago
The reference to query client should not change. If it does, you don’t have a stable queryClient. Also: why should reinstantiating this callback trigger the functions inside? Is it used in a useEffect?
fascinating-indigo
fascinating-indigoOP17mo ago
It is used in a useEffect inside the component it is passed to I think just adding the above tracing code will help me figure out if this function even being called multiple times. It may very well not. Alright, added that to prod. See what we find. Hopefully i see it being triggered many times, and I can narrow it down. If not, need to look elsewhere.
rival-black
rival-black17mo ago
what logging tool r u using ? winstion axiom idk if sentry helps sentry is a strong one but i mean its more for errors really
fascinating-indigo
fascinating-indigoOP17mo ago
Swntry
quickest-silver
quickest-silver16mo ago
useHotKeys

Did you find this page helpful?