Continuing on with the previous article’s theme of “What’s the Svelte way to handle X as I did in React?” we’re going to look at the useEffect hook. Below we have a couple of examples to highlight the similarities and differences between Svelte and React when it comes to implementing the useEffect pattern found in React.
For those of you unfamiliar with Svelte, I recommend you start with the Svelte basics tutorial.
As a refresher, “hooks” in React are functions that allow us to “hook” into the lifecycle of components. You can read about the motivation for creating hooks from the React docs. The useEffect hook is meant to allow us to execute some code after the component renders. By default useEffect runs after every render, but you can pass optional arguments to it to specify when it should run, allowing you to only run the useEffect callback function when certain things change.
It’s important to understand that Svelte, in contrast to React, does not have the concept of a “render cycle” where changing something in a component will cause the whole component to re-render. Because of this, the way Svelte handles the idea of executing code every time something changes can seem very different. For a better understanding of Svelte’s philosophy on “reactivity” in apps, this blog post gives a good overview of the design choices.
The Svelte Equivalent of React useEffect
Side effect on state change in React
import {useState, useEffect} from 'react';
export const PushPopArray = () => {
const [arr, setArr] = useState([1, 2, 3]);
const pushToArr = () => setArr([...arr, arr.length + 1]);
const popFromArray = () => setArr([...arr.slice(0, -1)])
useEffect(() => {
console.log(arr);
}, [arr])
return (
<>
<button onClick={pushToArr}>Push to Arr</button>
<button onClick={popFromArray}>Pop from Arr</button>
<ul>
{arr.map(el => <li>{el}</li>)}
</ul>
</>
)
}
This is a simple illustration of a side effect when a designated value changes. useEffect takes a callback function, and then an optional parameter of an array the elements of which determines when the callback will run. For this example, we want to run the callback on each change of “arr”.
Side effect on state change in Svelte
<script>
let arr = [1, 2, 3];
const pushToArr = () => arr = [...arr, arr.length + 1];
const popFromArr = () => arr = [...arr.slice(0, -1)];
$: console.log(arr);
</script>
<button on:click={pushToArr}>Push to Arr</button>
<button on:click={popFromArr}>Pop from Arr</button>
<ul>
{#each arr as el}
<li>{el}</li>
{/each}
</ul>
In Svelte, writing the same logging effect is actually simpler. We just need to use the “$:” syntax and provide the expression we want to run when its dependencies change. In this case, using the “arr” variable in the expression is enough for Svelte to know to rerun the expression when “arr” changes.
Fetching “on mount” in React
import {useState, useEffect} from 'react';
export const PushPopArray = () => {
const [arr, setArr] = useState([1, 2, 3]);
const [data, setData] = useState({});
const pushToArr = () => setArr([...arr, arr.length + 1]);
const popFromArray = () => setArr([...arr.slice(0, -1)])
// do something with our fetched data
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=151`)
const json = await res.json();
setData(json);
};
fetchData();
}, [])
return (
<>
<button onClick={pushToArr}>Push to Arr</button>
<button onClick={popFromArray}>Pop from Arr</button>
<ul>
{arr.map(el => <li>{el}</li>)}
</ul>
</>
)
}
Inside our useEffect now we are fetching from some API. We’re also using a useState hook to set up a variable we can put the data into once we have it. The empty array as the second argument to useEffect means the fetch will run once the component mounts.
Fetching “on mount” in Svelte
<script>
import { onMount } from "svelte";
let arr = [1, 2, 3];
const pushToArr = () => arr = [...arr, arr.length + 1];
const popFromArr = () => arr = [...arr.slice(0, -1)];
let data;
onMount(async () => {
const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=151`)
const json = await res.json();
data = json;
});
// do something with our fetched data
$: console.log(arr);
</script>
<button on:click={pushToArr}>Push to Arr</button>
<button on:click={popFromArr}>Pop from Arr</button>
<ul>
{#each arr as el}
<li>{el}</li>
{/each}
</ul>
Svelte has its own dedicated “onMount” function that you can use to run something only on the component mount (this should seem familiar to anyone who worked in React before hooks). The fetch pattern is the same except at the end when we assign our data explicitly to our “data” variable. We’re still following the “let” pattern of declaring our variables upfront so we can reassign them later when we need to.
Conclusion
Like with other patterns from React, the basic idea of how to replicate the useEffect pattern in Svelte isn’t too challenging. However, Svelte has its own patterns and syntax for a reason. The useEffect hook is predicated on the idea of a render cycle. Svelte doesn’t have a virtual DOM with its render/rerender cycle. While direct comparisons like the above examples are good starting points, for anyone seriously considering Svelte for projects the sooner you embrace the Svelte way of doing things the easier the transition will become.