It is Strict Mode, and it is deliberate. In development React mounts the component, unmounts it and mounts it again, specifically so that effects which are not safe to run twice show themselves now rather than in production under a future feature.
So do not silence it — it is telling you the effect has no cleanup. Your fetch is the classic case: two requests race, and the slower one can resolve last and overwrite fresher state. Abort on cleanup:
useEffect(() => {
const controller = new AbortController();
fetchThings({ signal: controller.signal })
.catch((e) => {
if (e.name !== 'AbortError') throw e; // a cancelled fetch is not an error
});
return () => controller.abort();
}, []);
Once every effect cleans up after itself, the double invocation stops mattering — which is the point of it.
Worth adding that it only double-invokes in development. Disabling Strict Mode to make the logs tidy trades a warning you can act on for a bug you will find in production instead.