Introduction: The Status Quo vs. The Future
Any reasonable developer in today's world would learn React because it's the status quo and that's where the jobs are. But life is a lot more fun when you become unreasonable and go against the status quo to push web development forward for future generations.
In today's video, we'll compare React and Svelte side-by-side by looking at common patterns and design choices by the framework creators. I'm not here to tell you that one is better than the other because that's what the comment section below this video is for. The only way to truly find out which one is best is to build something with both of them and decide for yourself.
I just released a brand new full SvelteKit course yesterday and also have a full Next.js 13 course for Fireship Pro members. Going through each one of these courses will give you a really good idea of which framework is best for you.
Rendering: Virtual DOM vs. Compiler
First up, we need to talk about rendering. Both of these frameworks do the same thing: they help developers build reactive UIs with JavaScript by organizing code into reusable components. An end user would never be able to tell the difference between the two, but when it comes to the developer experience, there is a world of difference.
React.js uses a runtime called the Virtual DOM. It keeps track of data changes in the application in order to render them in the actual DOM in the browser. The drawback is that this runtime requires some initial JavaScript, and in frameworks like Next.js your baseline is around 70 kilobytes just to render a 'Hello World'.
Svelte, on the other hand, takes an entirely different approach, using a compiler to eliminate the need for a runtime. It takes your Svelte code and converts it into vanilla JavaScript, which results in a far smaller 'Hello World'. Using a compiler, though, is kind of like cheating. React.js is just JavaScript, whereas Svelte can take non-JavaScript code to allow developers to do things more efficiently than they could otherwise. But some JavaScript fundamentalists might consider this black magic. What's ironic, though, is that vanilla JavaScript libraries tend to be much easier to work with in Svelte when compared to React. However, React does have a massive ecosystem of dedicated libraries to help you get things done.
Component State
Now let's look at an actual code example of component state. Here we have a basic counter app. In React, we use functions to create components and then add reactive state to them with the useState hook that returns a reactive value and a setter function to update the state. Pretty simple, but let's see how it compares to Svelte. Here on the right side, in Svelte you have only one component file and define the logic within the script tags. To create reactive state, all you do is create a variable with the let keyword. From there, we can define a function on the click event that mutates the value directly.
The Svelte code is able to be more concise because it doesn't require any imports or function calls to initialize the state. It looks and feels like vanilla JavaScript, but that's just an illusion.
Passing Props
So now let's look at how props work between the two frameworks. To pass props in React, we do so by defining them as function arguments, which are typically destructured like so. In Svelte, things look a lot different. Putting the export keyword in front of a variable allows it to be passed in from the outside. On the other side, using props looks basically identical in both frameworks, although Svelte does use some syntactic sugar allowing you to more easily match variable names to props. When it comes to props, though, one thing you can do in React that you can't do in Svelte is pass components as props.
Children and Slots
And that brings us to our next comparison: children. In React, because we can pass components as props, we can render them directly in the JSX. In addition, if we want to insert UI inside of a component, we can use the built-in props.children value.
Now, in Svelte, we have an entirely different system called slots. The default slot is the equivalent to props.children. However, you can also create named slots that allow you to insert UI at specific points in this component's markup. That gets the job done, but I do kind of miss the ability to use components as props.
Component Initialization
And so now let's look at how we might run code when a component is initialized. In React, we have the useEffect hook, which takes a callback function followed by an empty array to signify that it doesn't have any dependent data, so it only runs once. In Svelte, we have a similar pattern with the onMount function. It's more readable, but more importantly, it can handle an async function, which is not possible in React, which means you need to jump through this extra hoop of defining your own async function that's separate from the main callback.
Side Effects & Computed State
But now we're going to see a much bigger divergence between these two frameworks with side effects and computed state. In React, we can create a side effect with the useEffect hook again that updates the document title anytime the count changes. We just need to tell it to watch the count by putting it in the dependencies array. In Svelte, we have an entirely different mechanism called reactive declarations that start with a dollar sign and a colon. This looks kind of weird at first, but what it tells Svelte to do is rerun the code whenever any dependent data changes. In this case, the compiler knows that this code is dependent on the count value; therefore, it updates the document title whenever the value changes. Not only is it more concise, but it also tends to be more reliable than React because it's easy to screw up the dependencies array and get unexpected results that are hard to debug. That's just one of the reasons they call it useEffect gun.
But we can also use reactive declarations for computed state. In React, you can easily create computed state by simply defining a variable that's dependent on some state. The problem is that this code will run every time this component is re-rendered. Therefore, if you have an expensive computation, you'll need to wrap this code in useMemo and once again tell it explicitly which data it depends on. This will cache or memoize the value between renders. In Svelte, you don't even have to memorize the word memoize. We can just use the same reactive declaration as before to define a new variable. Again, it automatically knows to only run this code when the count changes.
Templating and Conditional Logic
That's pretty cool, but now let's look at some differences in templating, starting with conditional logic. React uses JSX, where you put your HTML in your JavaScript, whereas Svelte has its own templating approach where you bring JavaScript into your HTML. JSX is one of React's great innovations. If you like some of the ideas of Svelte but don't like its templating system, a good alternative is Solid.js, which is kind of like React with a Svelte-style compiler.
Conditional Logic: Ternaries vs. If Blocks
When it comes to conditional logic in React, we can't directly use an if statement because a function component needs to return a JavaScript expression that represents a single value. To represent a basic if-else statement or true/false situation, we can use a ternary operator and represent everything with a single line of code. Now remember, Svelte uses a compiler, which means it can come up with any kind of templating magic that it wants to. It allows you to create if-else statements in the HTML very similar to how you would in normal JavaScript. It's a bit more verbose, but more readable in my opinion. And the readability is very noticeable when you have multiple conditions like an if-else statement, because in React you'll occasionally see nightmare code like this of nested ternaries, although there are better ways of doing this like extracting the logic to its own dedicated component.
Loops: .map() vs. {#each}
And now let's take a look at loops. The most common way to loop in React is to use the map function on an array. This allows you to define a callback function that returns the UI for each item in that array. In addition, we can make it a keyed loop by adding a key prop to the child. In Svelte, we can loop over an array of data. Within each loop, it creates a template variable for each item, and then we can use its data inside the tags. To make it a keyed loop, we can add parentheses with the value for the key inside of the each block.
Shared State Management
Now let's switch gears to the big complicated topic of shared state. Like, how do I take one reactive value and share it throughout the entire component tree? React doesn't really have a primitive way to do this out of the box, and you'll typically need to bring in a state management solution like MobX or Redux, or my personal favorite, Jotai, which means 'state' in Japanese. With Jotai, we create an atom to represent a value, then in one or more components, we can use the useAtom hook to access that value instead of using the built-in useState hook. In React, we can now use that state in multiple places because it's been decoupled from the component.
Svelte has a similar mechanism built-in called stores, which are very similar to observables in libraries like RxJS. We can create a writable store to represent a value. What's crazy, though, is that we can then subscribe to the value of that store inside of any component, both in the templates and in the JavaScript. All we have to do is put a dollar sign in front of it, thanks to the magic of the compiler. As a Svelte user myself, I can't even begin to tell you how much complexity and code this one little mechanism will eliminate from your codebase. It allows you to use reactive data throughout the entire application with surgical precision and zero boilerplate. On top of that, it knows when to automatically unsubscribe from data when it no longer has any listeners, and that can be extremely important when using real-time data like Firestore, for example.
Handling Async Data & Promises
Speaking of async data, let's talk about promises. React has a new use hook that's currently experimental that can be used to unwrap promises directly in a component. It's essentially the equivalent to the await keyword, which resolves the value of the promise into the variable. However, you'll also likely want to show a loading state and handle errors. One way to do that is to wrap this component with Suspense, which will render this loading spinner as a fallback while the promise is being resolved. And then we could wrap all of that in an error boundary to show an error page if it's rejected. This code is a bit intimidating and requires a lot of knowledge of React to even understand what the hell is going on.
In Svelte, we can actually unwrap promises directly in a template using await. While awaiting, show a loading spinner. Then when the number resolves, show the main UI. Otherwise, if there's an error, show the error UI. It's easily understandable for any JavaScript developer who knows promises.
Conclusion & Full Courses
And finally, if you want to see some full-stack comparisons on the server, become a Fireship Pro member to get access to these full courses. Huge thanks to everyone who's already supported my work there, and today is the last day to get 35% off a membership using that code below. Thanks for watching, and I will see you in the next one.