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 useReduceruseStateuseLayoutEffectuseEffectuseDebugValue
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. Child2thingthingChild2const setThing=(v)=>thing=v;
Another difference is that the React thingconst
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 $$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 useEffectuseEffectscomponentDidMountcomponentWillUnmount
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 useEffectafterUpdate
The callback which is passed to useEffect
useEffect(()=>{ doSomething(a,b); },[a,b])
Can often be translated to a simple reactive call:
$: doSomething(a,b);
The reason is that when abdoSomethingbelowHalfbelowHalf
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 createContext
const someContext = React.createContext();
into
const someContext = "someCntextUniqueString";
Then you could think that the useContext
const value = useContext(someContext);
to
let value = getContext(someContext);
And that can work occasionally, but in general, there is a caveat: if the valuevalueuseState
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 useCallback
const cb = useCallback((a) => foo(a,b),[b])
Usually you can just use 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 absomeFunction
useRef
In React, useRefletcurrent.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 FancyListscrollTo(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 useImperativeHandleexport const
/// FancyList.svelte <script> export const scrollTo = (itemID)=>{...} </script> ...
In a parent component you can get the reference to the FancyListbind:thisscrollTo
/// 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?