Stephen Surtees Portrait
Stephen Surtees software engineer

Reacting to Svelte

First impressions of Svelte, porting a simple React app.

The Mission

  1. Write a simple React application that has some drag and drop functionality.
  2. Port the React application to Svelte to get a basic understanding of the framework.
  3. Write a blog post on the experience, hello there.
  4. Spend less than two days of time on the project.

The Result

A side by side comparison of Svelte and React, the same application is visible with only minor visual differences

Both applications are available in a single repository.
React and Svelte code on GitHub
Try out the React app Live

The Beginning

It’s been nearly a year since I’ve written any React, but it is a framework I have considerable experience with. Svelte on the other hand is completely new to me. I’d heard it is similar to Vue, and this small project certainly confirms that perception for me. Most frameworks these days have converged on a similar set of ideas, and transitioning to a new one is usually fairly straightforward.

Understanding the context around any given project is important. Svelte is a framework I’ve been meaning to look at for a while, and an upcoming interview added motivation to give it a try. I had a single weekend to create the React application, and then port it to Svelte. Whenever I do a project like this for learning purposes, I try to also make it personally useful if I can. The application is a basic tool for calculating district adjacency bonuses for the video game Civilization 6. I’ve been enjoying the game recently with my partner, and like the idea of being able to quickly test some combinations on my phone while playing.

The stage is set. We have a goal, a deadline, and can make some decisions about trade-offs.

Trade-offs

One of the biggest problems in Software Engineering is the idea that one tool or way of doing things is inherently better than another. Context is always important. In this project, my time was limited, and I’m primarily interested in learning the basics of Svelte, while also refreshing my memory of React. As such, I make most of the logic as dumb as possible, and not extensible. It does not make sense to spend time on activities that will not further our goals. When porting the code from React to Svelte, I copy paste as much as possible, and do not waste time trying to share files or assets between the two projects in a cleaner way.

Now. Having bootstrapped both applications with npm create vite@latest, lets look at some of the code.

The TileOption component represents an icon that can be dragged onto a larger hexagonal slot.

TileOption.tsx (React)

import { useDraggable } from '@dnd-kit/core';

interface TileOptionsProps {
    tile_type: string;
    thumbnail: string;
    class_name: string;
    id_override?: string;
}

export const TileOption: React.FC<TileOptionsProps> = (props) => {
    const { attributes, listeners, setNodeRef, transform } = useDraggable({
        id: props.id_override || props.tile_type,
    });

    const style = transform ? {
        transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
    } : undefined;

    return (
        <div
            ref={setNodeRef}
            style={{touchAction: "none"}}
        >
            <img 
                style={style}
                {...listeners}
                {...attributes}
                src={props.thumbnail}
                alt={props.tile_type}
                className={props.class_name} 
            />
        </div>
    )
}

TileOption.svelte (Svelte)

<script lang="ts">
    import { draggable } from '@thisux/sveltednd';

    interface TileOptionsProps {
        tile_type: string;
        thumbnail: string;
        class_name: string;
        id_override?: string;
        handle_drop(): void;
    }

    let props: TileOptionsProps = $props();
</script>

<div
    style="touch-action: none;"
    use:draggable={{ 
        container: props.id_override || props.tile_type,
        callbacks: { onDrop: props.handle_drop },
        dragData: {id: props.id_override || props.tile_type},
    }}
>
    <img 
        src={props.thumbnail}
        alt={props.tile_type}
        class={props.class_name} 
    />
</div>

The Svelte code is marginally shorter (28 lines vs 34 for React). Each component uses a different library for drag and drop. This is another example of a trade-off. I’ve implemented my own drag and drop functionality from scratch before, but it simply doesn’t make sense for the majority of cases, especially when time is short. It’s also worth exploring the ecosystem of each framework. I certainly had more options for drag and drop in React than Svelte.

Much like in Vue, the Svelte component is closer to Vanilla Javascript than React. All Typescript resides in the script tag. We have a template syntax that is similar to Jsx, but notably closer to raw HTML, note the prop class vs className.

An Extract from App.svelte

const grid: (TileType | null)[] = $state([
    null,
    null,
    null,
    null,
    null,
    null,
    null,
]);

function clear_tile(index: number) {
    grid[index] = null;
}

function set_tile(index: number, tile: (TileType | null)) {
    grid[index] = tile;
}

The hard coded array of seven nulls is another example of “doing thing’s the dumb way” because it was the easiest and fastest solution at the time. On reflection, the following would be much better.

const grid: (TileType | null)[] = $state(new Array(7).fill(null));

More importantly, we see some key differences with React. The $state is an example of Svelte5 rune syntax. It is similar to Reacts useState hook, however it only returns a single value, rather than a value and a setter.

const [grid, setGrid] = useState<(TileType | null)[]>(new Array(7).fill(null));

As you can see, in Svelte, we can directly mutate the value of grid without breaking our reactivity, there is no need for the separate setter. Features like this, combined with being closer to Vanilla code, is why Svelte and Vue often feel less boiler platey than React.

Svelte also has an $effect rune that is very similar to Reacts useEffect hook, the main difference being that $effect infers the dependencies wheras useEffect requires a list of values as the second parameter.

One thing to be careful with is calculating new values from $state. The classic Svelte example is as follows.

let count = $state(0);
let doubled = $derived(count * 2);

Note that the $derived rune provides memoization for the doubled value. The $derived rune allows Svelte to cache values, only recalculating them when a dependency changes.

React is built on a full virtual dom. Svelte does most of it’s work at compile time, resulting in minimal JavaScript and faster code. This is why the syntax is different for things like state and effect, but the general idea is all the same. You have state information, you can mutate it, other things can react to it. All of these frameworks allow you to build front end web applications, it’s no surprise that many of their constructs are similar, even if they go about things a little differently.

Vue has less boiler plate than react, but I haven’t spent nearly enough time yet with Svelte to make the same claim for it, though it appears highly likely. I’d enjoy working on a larger project in Vue or Svelte to see how they compare to React.

This may all sound like a bunch of negatives for React, but React is still a very worthy framework. It is mature, has the largest ecosystem out of the JavasScript frameworks, and it’s easy to hire for. More importantly to me, I’ve worked on very large React projects, and the framework never got in the way, though I’ve certainly had to debug some strange behaviour with custom hooks and weirdly timed state updates. React allowed me to build very large, feature rich applications that ran well, looked good, and served their target audience well. If Vue or Svelte allow you to do the same, that is all that really matters.

Conclusion

I need to play around more with Svelte, but it’s certainly promising, and I like the idea of doing more of the processing at compile time. Svelte also puts Vue in a weird place for me. Like a half way point between React and Svelte. I’m not sure why one would choose Vue over Svelte right now, assuming the decision to move away from something like React was due to speed or bloat. I’ll have to learn more about the tools before coming to any concrete opinions on this.

The resulting Javascript size for each build varied greatly. React came in at 225KB, while Svelte was only 35KB. This is a huge difference. I’ve always aimed for minimal websites, it’s why this blog is written in Hugo, and I’d be very interested to see how these numbers change with a larger App.

I’m completely new to Svelte, and a little out of practice with JavaScript at the moment. For the past year I’ve been focusing heavily on Rust, which I think is a superb language. I left my hard coded seven null example intentionally, as it’s what happens when you focus on one tool for too long to the detriment of another. Computer science is so vast, and the number of languages and frameworks so large, that it is unrealistic to expect perfect code in any language we’ve ever used. It is simply enough to recognise when your code is subpar, and re-learn what the better approach would be.