useEffect
The useEffect
hook lets you perform side effects in function components. It serves a similar purpose to lifecycle methods in class-based component systems, but unified into a single API.
Basic Usage
useEffect(() => {
// Side effect code
// Optional cleanup function
() => {
// Cleanup code
}
}, dependencies)
Where: - The first argument is a function that contains the side effect code - The function can optionally return a cleanup function - The second argument is an optional array of dependencies
Timing of Effects
Effects run after the browser has painted, so they don’t block the browser from updating the screen. This makes your app feel more responsive.
Types of Effects
Effects Without Cleanup
Some effects don’t require any cleanup:
val DocumentTitleComponent = (props: TitleProps) => {
useEffect(() => {
// Update the document title using the browser API
document.title = props.title
}, Seq(props.title))
// ...
}
In this example, the effect runs when the component mounts and whenever props.title
changes. There’s no need for cleanup because updating the document title doesn’t create any persistent resources.
Effects with Cleanup
Some effects create resources that need to be cleaned up:
val MouseTracker = () => {
val (position, setPosition, _) = useState((0, 0))
useEffect(() => {
// Set up event listener
val handleMouseMove = (e: dom.MouseEvent) => {
setPosition((e.clientX, e.clientY))
}
document.addEventListener("mousemove", handleMouseMove)
// Return a cleanup function
() => {
document.removeEventListener("mousemove", handleMouseMove)
}
}, Seq())
div(s"Mouse position: ${position._1}, ${position._2}")
}
The cleanup function will run: - Before the component unmounts - Before the effect runs again (if the dependencies change)
Controlling When Effects Run
The dependencies array controls when the effect runs:
-
No dependencies array: The effect runs after every render
scala useEffect(() => { console.log("This runs after every render") })
-
Empty dependencies array: The effect runs only once after the initial render
scala useEffect(() => { console.log("This runs only on mount") }, Seq())
-
Dependencies specified: The effect runs after the initial render and when any dependency changes
scala useEffect(() => { console.log(s"The count is now: $count") }, Seq(count))
Examples
Data Fetching
val UserProfile = (props: UserProfileProps) => {
val (user, setUser, _) = useState(Option.empty[User])
val (loading, setLoading, _) = useState(true)
val (error, setError, _) = useState(Option.empty[String])
useEffect(() => {
setLoading(true)
setError(None)
// Fetch user data
fetchUser(props.userId)
.map(userData => {
setUser(Some(userData))
setLoading(false)
})
.recover {
case e: Exception =>
setError(Some(e.getMessage))
setLoading(false)
}
}, Seq(props.userId))
div(
if (loading) {
div("Loading...")
} else if (error.isDefined) {
div(s"Error: ${error.get}")
} else if (user.isDefined) {
div(
h1(user.get.name),
p(user.get.email)
)
} else {
div("No user data")
}
)
}
Subscriptions
val ChatRoom = (props: ChatRoomProps) => {
val (messages, setMessages, updateMessages) = useState(Vector.empty[Message])
useEffect(() => {
// Subscribe to chat room
val subscription = chatService.subscribe(props.roomId, message => {
updateMessages(prev => prev :+ message)
})
// Cleanup: unsubscribe when component unmounts or roomId changes
() => subscription.unsubscribe()
}, Seq(props.roomId))
// ...
}
DOM Manipulation with Refs
val AutoFocusInput = () => {
val inputRef = useRef[dom.html.Input]()
useEffect(() => {
// Focus the input element after mounting
inputRef.current.focus()
}, Seq())
input(
ref := inputRef,
typ := "text",
placeholder := "This input will be focused"
)
}
Timers
val Timer = () => {
val (seconds, setSeconds, _) = useState(0)
useEffect(() => {
val interval = dom.window.setInterval(() => {
setSeconds(seconds + 1)
}, 1000)
// Cleanup: clear the interval when unmounting
() => dom.window.clearInterval(interval)
}, Seq())
div(s"Timer: $seconds seconds")
}
Common Patterns
Skipping Effects
Sometimes you want to run an effect conditionally. While you can’t put useEffect
inside a conditional, you can put the conditional logic inside the effect:
useEffect(() => {
if (shouldRun) {
// Only run the effect if shouldRun is true
doSomething()
}
}, Seq(shouldRun))
Separating Concerns
If you have multiple unrelated effects, split them into separate useEffect
calls:
val UserDashboard = () => {
// Track page views
useEffect(() => {
analytics.trackPageView("dashboard")
}, Seq())
// Fetch user data
useEffect(() => {
fetchUserData()
}, Seq())
// Set up event listeners
useEffect(() => {
// Event listeners setup and cleanup
}, Seq())
// ...
}
This makes your code more maintainable by keeping related logic together.
Implementation Details
In Fluxus, useEffect
is implemented as a hook that registers an effect function and its dependencies. After the component renders and the DOM is updated, Fluxus runs all the effects whose dependencies have changed.
Effects are run in order from parent to child components, and cleanup functions are run in reverse order to ensure a consistent data flow.