Side effects are operations that interact with the outside world instead of only returning a value: API requests, event listeners, timers, subscriptions, direct DOM access, and storage writes. Both Vue 3 and React give you tools for starting and cleaning up these effects, but the mental models differ.
The important habit is the same in both frameworks: every effect should have a clear start condition and a clear cleanup path.
What counts as a side effect
Common side effects:
- Fetching data from an API.
- Adding browser event listeners.
- Starting
setTimeoutorsetInterval. - Opening WebSocket or subscription connections.
- Touching DOM APIs directly.
- Reading or writing localStorage.
Rendering calculations are not side effects. If a value can be computed from props or state during render, do that instead of creating an effect.
Vue 3: lifecycle and watchers
Vue 3's Composition API gives you lifecycle hooks and watchers:
<script setup>
import { onMounted, watch, ref } from "vue"
const count = ref(0)
onMounted(() => {
console.log("mounted")
})
watch(count, (value) => {
console.log("count changed", value)
})
</script>
Use onMounted when the effect starts after the component is mounted. Use watch when the effect depends on reactive data changes.
Vue cleanup
Timers and listeners must be cleaned up:
<script setup>
import { onMounted, onUnmounted, ref } from "vue"
const count = ref(0)
let timerId
onMounted(() => {
timerId = window.setInterval(() => {
count.value += 1
}, 1000)
})
onUnmounted(() => {
window.clearInterval(timerId)
})
</script>
If an effect is tied to a watcher, put cleanup in the watcher flow so outdated work does not keep running.
React: useEffect
React uses useEffect for synchronization with external systems:
import { useEffect, useState } from "react"
function CounterLogger() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log("mounted or count changed", count)
}, [count])
return <button onClick={() => setCount((value) => value + 1)}>{count}</button>
}
The dependency array declares which values the effect uses. Do not remove dependencies just to silence ESLint; fix the data flow instead.
React cleanup
Return a cleanup function from useEffect:
import { useEffect, useState } from "react"
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = window.setInterval(() => {
setCount((value) => value + 1)
}, 1000)
return () => window.clearInterval(timerId)
}, [])
return <div>{count}</div>
}
React runs cleanup before rerunning the effect and when the component unmounts.
Abort outdated requests
Use AbortController to prevent outdated requests from updating state:
useEffect(() => {
const controller = new AbortController()
async function load() {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
})
const data = await response.json()
setResult(data)
}
load().catch((error) => {
if (error.name !== "AbortError") console.error(error)
})
return () => controller.abort()
}, [query])
The same browser API can be used in Vue when a watcher or lifecycle hook starts a request.
Common mistakes
- Using effects to compute values that could be derived during render.
- Forgetting to remove event listeners.
- Starting intervals without clearing them.
- Ignoring outdated async requests.
- Hiding dependency problems by disabling lint rules.
- Mixing data fetching, DOM work, and unrelated state updates in one effect.
Related FreeMac guides
- For React Hook selection, read React Hooks Guide: State, Effects, Context, and Reducers.
- For Vue reactivity basics, read Vue 3 Reactivity: ref, reactive, and toRefs.
- For JavaScript scheduling, read JavaScript Event Loop: Tasks, Microtasks, and Rendering.
Continue reading
React Portal: Render Modals Outside Parent DOM
Use React Portal for modals, dropdowns, tooltips, drawers, and toasts that must escape overflow, stacking context, or local layout boundaries.
14 min readReact Hooks Guide: State, Effects, Context, and Reducers
Use React Hooks by responsibility: useState for local state, useEffect for external synchronization, useReducer for complex transitions, and useContext for shared values.
8 min readVue 3 Reactivity: ref, reactive, and toRefs
Choose between ref, reactive, and toRefs in Vue 3 by looking at primitive values, objects, destructuring, and whether reactivity must survive extraction.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.