Lesson 1
Reactive Side Effects with `$effect`
Introduction to Reactive Side Effects

Welcome to the first lesson of the course Svelte: Reactivity & State Management! In this lesson, we’ll dive into one of the most powerful features of Svelte: reactive side effects. Reactive side effects are actions that happen automatically when your application’s state changes. For example, if you’re building a countdown timer, you might want to update the timer every second. This is where the $effect rune comes in. It allows you to manage these side effects in a clean and efficient way.

Think of $effect as a way to say, “Whenever this value changes, do this.” It’s like setting up a watchman who keeps an eye on specific data and takes action when it changes. In this lesson, we’ll use $effect to build a countdown timer, and you’ll learn how to handle side effects like timers, event listeners, and more.

By the end of this lesson, you’ll understand how to use $effect to manage side effects and ensure your application runs smoothly. Let’s get started!

Understanding the `$effect` Rune

The $effect rune is a key part of Svelte’s reactivity system. It allows you to run code whenever certain values change. Here’s a simple example to illustrate how it works:

svelte
1<script> 2 let count = $state(0); 3 4 $effect(() => { 5 console.log(`Count is now: ${count}`); 6 }); 7</script> 8 9<button onclick={() => count++}>Increment</button>

In this example, every time the count variable changes, the $effect rune logs the new value to the console. The $effect rune automatically tracks dependencies, so you don’t need to manually specify which values to watch. This makes your code cleaner and easier to maintain.

The $effect rune is especially useful for managing side effects like timers, event listeners, or API calls. In the next section, we’ll explore how to use $effect for cleanup to prevent memory leaks or unwanted behavior.

Using `$effect` for Cleanup

When using $effect, it’s important to clean up after yourself to avoid memory leaks or unexpected behavior. For example, if you set up a timer using setInterval, you should clear it when it’s no longer needed. Here’s how you can do that:

svelte
1<script> 2 let timeLeft = $state(10); 3 let running = $state(false); 4 5 $effect(() => { 6 if (!running) return; 7 8 const id = setInterval(() => { 9 if (timeLeft > 0) { 10 timeLeft -= 1; 11 } else { 12 running = false; 13 } 14 }, 1000); 15 16 return () => { 17 clearInterval(id); // Cleanup the interval 18 }; 19 }); 20</script> 21 22<button onclick={() => running = true}>Start</button> 23<button onclick={() => running = false}>Stop</button> 24<p>Time left: {timeLeft}</p>

In this example, the $effect rune sets up a timer that counts down from 10. When the timer is no longer needed (e.g., when the user clicks “Stop”), the cleanup function (clearInterval) is called to stop the timer. This ensures that your application doesn’t waste resources or behave unexpectedly.

Cleanup is a crucial part of using $effect, and it’s something you’ll need to consider whenever you’re managing side effects. In the next section, we’ll explore how to use the untrack function to avoid unnecessary re-runs.

Combining `$effect` with `untrack`

Sometimes, you might want to run code inside $effect without tracking dependencies. This is where the untrack function comes in. It allows you to run code without triggering a re-run of the $effect when the values change. Here’s an example:

svelte
1<script> 2 import {untrack} from "svelte" 3 let timeLeft = $state(10); 4 let running = $state(false); 5 let startCount = $state(0); 6 7 $effect(() => { 8 if (!running) return; 9 10 const id = setInterval(() => { 11 if (timeLeft > 0) { 12 timeLeft -= 1; 13 } else { 14 running = false; 15 } 16 }, 1000); 17 18 return () => { 19 clearInterval(id); 20 }; 21 }); 22 23 $effect(() => { 24 if (running) { 25 untrack(() => { 26 startCount++; 27 }); 28 } 29 }); 30</script> 31 32<button onclick={() => running = true}>Start</button> 33<button onclick={() => running = false}>Stop</button> 34<p>Time left: {timeLeft}</p> 35<p>Total starts: {startCount}</p>

In this example, the untrack function is used to increment the startCount variable without triggering a re-run of the $effect. This is useful when you want to perform an action that doesn’t depend on the reactive state.

By combining $effect with untrack, you can create more efficient and predictable code. In the next section, we’ll put everything together to build a complete countdown timer.

Building the Countdown Timer

Now that you understand the basics of $effect and untrack, let’s build a complete countdown timer. Here’s the code we’ll be working with:

svelte
1<script> 2 let timeLeft = $state(10); 3 let running = $state(false); 4 let startCount = $state(0); 5 6 $effect(() => { 7 if (!running) return; 8 9 const id = setInterval(() => { 10 if (timeLeft > 0) { 11 timeLeft -= 1; 12 } else { 13 running = false; 14 } 15 }, 1000); 16 17 return () => { 18 clearInterval(id); 19 }; 20 }); 21 22 $effect(() => { 23 if (running) { 24 untrack(() => { 25 startCount++; 26 }); 27 } 28 }); 29</script> 30 31<h3>Countdown Timer</h3> 32<button onclick={() => running = true}>Start</button> 33<button onclick={() => running = false}>Stop</button> 34<button onclick={() => { timeLeft = 10; running = false; }}>Reset</button> 35<p>Time left: {timeLeft}</p> 36<p>Total starts: {startCount}</p>

This code creates a countdown timer that starts at 10 seconds. The $effect rune manages the timer, and the untrack function increments the startCount variable without triggering a re-run. The timer can be started, stopped, and reset using the buttons.

When you run this code, you’ll see the timer count down from 10, and the “Total starts” counter will increase each time you start the timer. This example demonstrates how to use $effect and untrack together to manage side effects and state in a Svelte application.

Below is a preview of how the timer will look when running:

Best Practices for `$effect` in Svelte

Using $effect efficiently ensures that your Svelte applications remain performant and free of common pitfalls. Follow these best practices to manage reactive side effects effectively:

  • Use $effect only for side effects. Avoid using it for derived values; use $derived instead.
  • Always return a cleanup function. When using setInterval, event listeners, or subscriptions, clean them up to prevent memory leaks.
  • Prevent infinite loops with untrack(). If modifying a reactive variable inside $effect, wrap the change with untrack() to avoid unnecessary re-runs.
  • Keep $effect logic simple. Break large effects into smaller, focused ones for better maintainability.
  • Avoid unnecessary $effect statements. If a $state or $derived can achieve the same result, use that instead for better performance.

By following these best practices, you’ll write cleaner, more efficient Svelte code while avoiding reactivity pitfalls. 🚀

Summary and Next Steps

In this lesson, you learned how to use the $effect rune to manage reactive side effects in Svelte. We covered the basics of $effect, how to use it for cleanup, and how to combine it with untrack to avoid unnecessary re-runs. You also built a functional countdown timer using these concepts.

Here are the key takeaways:

  • Use $effect to run code whenever specific values change.
  • Always clean up after side effects (e.g., clear intervals or unsubscribe from events) to prevent memory leaks.
  • Use untrack to run code without tracking dependencies, making your code more efficient.

In the next lesson, we’ll explore more advanced topics in Svelte’s reactivity system. For now, try experimenting with the countdown timer code and see how you can modify it to add new features. Great job on completing this lesson—you’re well on your way to mastering Svelte!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.