Revalidation with concurrent local mutation #1143
-
Hello folks 👋 I ran into a problem where I though someone here might be able to help me. I have a query that's automatically revalidated every second using Here's a contrived example (also see the codesandbox) that shows the behavior. I'd expect the counter to be reset to zero after a successful revalidation, but it just keeps going up. import { useEffect } from "react";
import useSWR from "swr";
async function fetcher() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(0);
}, 1000);
});
}
export default function App() {
const { data, mutate } = useSWR("/count", fetcher, { refreshInterval: 1000 });
useEffect(() => {
const interval = setInterval(() => {
mutate((current) => (current || 0) + 1, false);
}, 300);
return () => clearInterval(interval);
}, [mutate]);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
} Can I somehow configure PS: If it helps I can also elaborate on my exact use-case, i.e. for what I need this behavior. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
A quick look into the source code revealed that this behavior is expected: https://github.com/vercel/swr/blob/master/src/use-swr.ts#L379-L390 My guess was correct, when there are local mutations overlapping with the revalidation request, the revalidation result is considered "not fresh" and thrown away. (This was implemented that way from the first commit onwards.) I'm not quite sure why tbh. I don't see any downsides when overriding local mutations with a revalidation result. (I.e. case 1 and 2 described in the code comment.) In case 3 I'd expect to cancel the local mutation instead, as it could be based on stale data and thus be stale itself. Of course I'm curious if anybody can explain to me why the current implement does indeed make sense 😉 |
Beta Was this translation helpful? Give feedback.
-
Well, not too much going on in here 😅 Still if somebody is interested, I solved this by wrapping import React from "react";
import useSWR from "swr";
const ClientDataContext = React.createContext();
const fetcher = url => fetch(url).then(r => r.json());
// Render this around the parts of the component tree that need the data
export function ClientDataContextProvider({ children }) {
const { data: serverData } = useSWR("/api", fetcher, { refreshInterval: 1000 });
const [clientData, setClientData] = useState(serverData);
// Always use the latest server-state if it changed
React.useEffect(() => {
setClientData(serverData);
}, [serverData]);
// This function never has to be redeclared
const mutate = React.useCallback((reducer) => {
setClientData(reducer);
}, []);
React.useEffect(() => {
const interval = setInterval(() => {
mutate((current) => {
const updates = { /* client logic */ };
return { ...current, ...updates };
});
}, 100);
return () => clearInterval(interval);
}, [mutate]);
return (
<ClientDataContext.Provider value={clientData}>
{children}
</ClientDataContext.Provider>
);
}
// Use this custom hook to
export function useClientData() {
return React.useContext(ClientDataContext);
} |
Beta Was this translation helpful? Give feedback.
Well, not too much going on in here 😅
Still if somebody is interested, I solved this by wrapping
useSWR
in a custom context & hook where I prioritize the results of the revalidation requests over any local mutations: