With functional components, React tried to establish a new approach for UI development. It was a risky move, but it was successful: a large group of developers jumped onto the train, who had to learn the new hook logic. I show how these hooks can be translated into Svelte code. This might be helpful, if you try to port some React code to Svelte, or if you want to understand the differences and similarities of both frameworks.
Introduction
In previous blog posts, I described how to port some React libraries, but so far all have been class old-school components only. Some time ago the React team decided, that they want to embrace the functional programming paradigm and today it seems to be en vouge to only use functional components.
Functional programming is an interesting concept with several ingredients, eg. immutability. In a general-purpose language like javascript, it is a lot about self-discipline. One of these rules is that whenever possible, you should structure your code into pure functions. Pure functions only depend on their inputs, are stateless, don't change anything outside of the function and always, for the same inputs, return the same results. If you are not so disciplined, React has so-called hooks for you.
Here is a list of built-in hooks:
- useState
- useEffect
- useContext
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
I will not cover
(similar to useReducer
), useState
(similar to useLayoutEffect
) and useEffect
(specific to React DevTools). You also can write custom how, I will not write about them here, they will be the topic of a follow-up post.
useDebugValue
useState
With this basic hook, you can add state to your component. An equivalent implementation in Svelte is more a question about the structure you want to in your code. If you have a basic React component
export const MyComponent = (props)=>{ const [thing, setThing] = useState(42); const anotherThing = doSomething(thing); return ( <Child1 a={thing} b={anotherThing}> <Child2 c={setThing}/> </Child> ) }
the simplest Svelte implementation would be
<script> let thing = 42; $: (anotherThing = doSomething(thing)); </script> <Child1 a={thing} b={anotherThing}> <Child2 bind:c={thing}> </Child1>
In this translation, you don't have an explicit setter function.
can change Child2
, but also sees the value. In React that could cause problems, because with every change in thing
thing
would re-render. In Svelte, code does not re-run by default, you have to mark it as reactive, so this implementation usually doesn't trigger unnecessary re-renders. But of course, if you prefer to have setters you are free to implement it yourself Child2
.
const setThing=(v)=>thing=v;
Another difference is that the React
is a thing
, which means you can't change it directly. This is where the immutability from the functional programming paradigm is applied. If you see a lot of value in it, I would recommend the following implementation using readable stores:
const
import {readable} from 'svelte'; const useState = (init) => { let set; const state = readable(init,(s) => { set = s; return () => set = undefined; } return [state,set];
This separates the value and the setter and you use it like in React with the one exception that you have to use the
symbol in front of your state variable (eg. $
), since it is a store.
$thing
useEffect
The name 'effect' comes from the term 'side-effect'. In functional programming, this is considered something you should try to avoid whenever possible. Yet, if you look into a few React libraries, there can be a lot of these. The
hook has quite a few different use-cases. When you transition from React class components, most lifecycle functions end up as useEffect
. useEffects
and componentDidMount
are implemented as an effect with an explicitly empty dependency array:
componentWillUnmount
useEffect(()=>{ window.addEventListener('keydown',handler); return ()=>window.removeEventListener(handler); },[])
This can be translated to:
import {onMount} from 'svelte'; onMount(()=>{ window.addEventListener('keydown',handler); return ()=>window.removeEventListener(handler); })
Another case is when
is used without a dependency array. It means that it should run after every update, so Sveltes useEffect
lifecycle comes to mind. This can work well, but it also might be a difference between Svelte and React, since React components can update more often.
afterUpdate
The callback which is passed to
can return another callback to clean up. A simple effect without cleanup-callback (the return value) like this:
useEffect
useEffect(()=>{ doSomething(a,b); },[a,b])
Can often be translated to a simple reactive call:
$: doSomething(a,b);
The reason is that when
or a
change, a subsequent state change inside b
has to wait for the render process to complete first due to the functional programming paradigm in React. In Svelte you can often trigger the changes right away. One exception is when state changes could cause a loop. In the following example, a random number between 0 and 1 is assigned to the state doSomething
. The reactive statement should be executed until belowHalf
gets a value smaller than 0.5. But with this implementation, that would not happen:
belowHalf
let belowHalf = 1; $: belowHalf = belowHalf < .5 ? belowHalf : Math.random();
The statement is only executed once because Svelte batches the reactive statements. Instead, you could implement it, so that it makes use of the component lifecycle, like in the
:
useEffect
import {tick} from 'svelte'; let belowHalf = 1; $: tick().then( _=> belowHalf = belowHalf < .5 ? belowHalf : Math.random() )
If you need a cleanup callback, you need to implement it yourself:
$: (_cleanup = (()=>{ if (_cleanup){ _cleanup(); } doSomething(a,b); return cleanup; })());
The tick version could look like this:
$:(_cleanup=tick() .then(_cleanup||Promise.resolve) .then(()=>{ doSomething(a,b); return cleanup; }) );
useContext
Both React and Svelte have a context API. For React you need to create a context object with
, while in Svelte a string is used as a key. For a 1:1 translation, you could change
createContext
const someContext = React.createContext();
into
const someContext = "someCntextUniqueString";
Then you could think that the
hook can be easily translated:
useContext
const value = useContext(someContext);
to
let value = getContext(someContext);
And that can work occasionally, but in general, there is a caveat: if the
is changed by the context provider in React, it triggers a re-render. As the Svelte tutorial suggests you should use a store as the value
. The only small difference then it's that the context consumer in Svelte is now able to change the context. If you don't want that I suggest applying a readable store pattern like in the last example in the value
section.
useState
Another problem is that Reacts create context can return a default value if no context provider is found. This can lead to errors, b if an object with attributes is expected. Here is how I would implement it:
const someContext = React.createContext(defaultObject);
import {writable} from 'svelte/store'; const someContext = () => writable(defaultObject); // In a script part of a Svelte component const value = getContext(someContext)||someContext();
useCallback
The components in React are always re-created on render. Sometimes you don't want to pass around a new reference for a function every time. In that case, you use
. In Svelte you don't have this constant re-creation, so if you define a function in a Svelte file, its reference would not change. Let's look at this example:
useCallback
const cb = useCallback((a) => foo(a,b),[b])
Usually you can just use
, but if you need (the reference to) cb to change, when a value in the dependency array updates, you can implement it like this:
let cb = foo
const _cb = (_b) => (a) => foo(a,_b); $: (cb = _cb(b));
useMemo
Ok, it is just that:
React:
const value = useMemo(someFunction, [a,b]);
Svelte':
$: (value = someFunction(a,b));
The only thing you must make sure it's that
and a
explicitly appear in what you write for b
, and if the React code violates the exhaustive Dependency array rule, that variables you don't want to trigger an update of value are not appearing explicitly.
someFunction
useRef
In React,
is a way to introduce a mutable value. In Svelte, mutable values are common,v just create them with useRef
. The ref in react is an object with the attribute let
, you can replicate this or keep in mind to remove the current
. I prefer the latter.
.current
useImperativeHandle
That's a catchy name, right? Let me explain the use of this hook, in case you never heard of it: When there were only React class components, you could add public member functions to it eg. on a
component you could add the function FancyList
. On the parent component, you would get a ref and call the function directly, so you could do something like this:
scrollTo(itemID)
const Parent = () =>{ const listRef = useRef(null); return <> <FancyList ref={listRef}/> <button onClick={()=>listRef.current?.scrollTo(500)}> }
To reach compatibility with the class based approach, the React team introduced the
hook. You can lookup the exact syntax here. Svelte components are a bit closer to class components than functional components, so it is pretty easy to replicate the functionality, by using useImperativeHandle
:
export const
/// FancyList.svelte <script> export const scrollTo = (itemID)=>{...} </script> ...
In a parent component you can get the reference to the
with FancyList
and call bind:this
:
scrollTo
/// Parent.svelte <script> let fancylist; </script> <FancyList bind:this={fancylist}/> <button on:click={()=>fancylist?.scrollTo(500)}>
Conclusion
Hooks are an important ingredient for React functional components. I show how you can replicate the behavior in Svelte and I give you code examples for the most common use-cases. If you encounter a case, which is not covered here, feel free to send me a note.
Most of the examples only work at the top level of a Svelte component. Custom hooks move the functionality into separate functions, which complicates the replication in Svelte. What to do in that case is the topic of an upcoming post.
Share this article on Twitter - Discuss on Reddit - Vote on next topics - Support me
This might also be interesting for you:
- How to Implement Custom React Hooks in Svelte
- Sveltes tick is my new best friend - porting react-textfit to Svelte
- tree-shake-css - project page
- How to merge cells with svelte-window
- Porting React to Svelte - From react-window 1:1 to svelte-window
- Poll: Which React library would you love to see in Svelte?
- Porting React components to Svelte - It might be easier than you think
- Svelte and Typescript
- How this blog is made - Basic Structure, Elder and Markdown
- How this blog is made part 2 - Make your website sketchy with Tailwind and Roughjs
- Showcase: The Descent Ripple Effect or The React Descent Ripple Effect
- Why are component libraries so complicated with React compared to Svelte?