loading...
Cover image for Comparing reactivity models - React vs Vue vs Svelte vs MobX vs Solid vs Redux
lloyds digital

Comparing reactivity models - React vs Vue vs Svelte vs MobX vs Solid vs Redux

hrastnik profile image Mateo Hrastnik Updated on ・13 min read

If you're reading this article you're probably already familiar with the concept of reactive programming, but just in case, let me explain what is it and why it's great.

When you're writing code, the commands get executed in a particular order - from top to bottom. So if you write...

let x = 10;
let y = x + 5;

Then y will equal 15, and that's just what we expect, but what happens to y if we then change the value of x to 20? The answer is simple - nothing happens to y, its value will still be 15.

The problem is that the second line of code doesn't say let y be the value of x plus 5. What it instead says is let y be the value of x at the moment of declaration, plus 5. That's because the values of x and y are not reactive. If we are to change the value of x, the value of y doesn't change with it.

let x = 10;
let y = x + 5;
let x = 20;

console.log(y); // 15

So how do we declare the variable y to be the value of x plus 5? That's where reactive programming comes in. Reactive programming is a way of programming that makes it possible solve this problem, but it's just a concept - the actual implementation can vary from library to library.

This article will compare some of the more popular reactivity models in the JS ecosystem - especially the ones found in the UI frameworks and libraries. After all, UI is just a function of state, meaning that UI has to react to changes in state.

In order to compare the different approaches to solving this problem, I'll demonstrate how to create a simple To-do app using different frameworks and libraries. We'll keep the UI as minimal as possible. After all, we are comparing reactivity models, and not UI libraries.

Here's how the end product is gonna look like.

Todo app

1. React

It's 2020 in the world of web development, so you've probably heard of React. It's a fantastic UI library, and, as its name would suggest, React can react to stuff. Namely, it can react to changes in state.

Here's how a basic todo app looks like in React.

import React, { useEffect, useState } from "react";

export default function App() {
  const [todoList, setTodoList] = useState([
    { id: 1, task: "Configure ESLint", completed: false },
    { id: 2, task: "Learn React", completed: true },
    { id: 3, task: "Take ring to Mordor", completed: true },
  ]);

  const completedTodoList = todoList.filter((t) => t.completed === true);
  const notCompletedTodoList = todoList.filter((t) => t.completed === false);

  function createTodo(task) {
    setTodoList([...todoList, { id: Math.random(), task, completed: false }]);
  }

  function removeTodo(todo) {
    setTodoList(todoList.filter((t) => t !== todo));
  }

  function setTodoCompleted(todo, value) {
    const newTodoList = todoList.map((t) => {
      if (t === todo) return { ...t, completed: value };
      return t;
    });
    setTodoList(newTodoList);
  }

  function addTodo() {
    const input = document.querySelector("#new-todo");
    createTodo(input.value);
    input.value = "";
  }

  useEffect(() => {
    console.log(todoList.length);
  }, [todoList]);

  return (
    <div>
      <input id="new-todo" />
      <button onClick={addTodo}>ADD</button>

      <div>
        <b>Todo:</b>
        {notCompletedTodoList.map((todo) => {
          return (
            <div key={todo.id}>
              {todo.task}
              <button onClick={() => setTodoCompleted(todo, true)}>
                Complete
              </button>
            </div>
          );
        })}
      </div>

      <div>
        <b>Done:</b>
        {completedTodoList.map((todo) => {
          return (
            <div key={todo.id}>
              {todo.task}
              <button onClick={() => removeTodo(todo)}>Delete</button>
              <button onClick={() => setTodoCompleted(todo, false)}>
                Restore
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

In React, reactive state is created using the useState hook - it returns the state itself, and a setter function to update the state.
When the setter is called the whole component re-renders - this makes it really simple to declare derived data - we simply declare a variable that uses the reactive state.

In the example above, todoList is a list of todo objects, each having a completed attribute. In order to get all the completed todos we can simply declare a variable and filter the data we need.

const completedTodoList = todoList.filter((t) => t.completed === true);

The state updater function can take the new state directly, or we can use an updater function that receives the state as the argument and returns the new state. We have to be careful not to mutate state so when we have some complex state like an object or an array we have to use some ugly tricks like in the setTodoCompleted function above.

It's possible to run a function whenever some reactive state changes using the useEffect hook. In the example we log the length of the todoList whenever it changes. The first argument to useEffect is the function we want to run, and the second is a list of reactive values to track - whenever one of these values changes the effect will run again.

There's one downside to Reacts reactivity model - the hooks (useState and useEffect) have to always be called in the same order and you can't put them inside an if block. This can be confusing for beginners, but there are lint rules that can help warn you if you accidentally make that mistake.

2. Vue

<template>
  <div>
    <input id="new-todo" />
    <button @click="addTodo">ADD</button>

    <div>
      <b>Todo:</b>
      <div v-for="todo in notCompletedTodoList" :key="todo.id">
        {{ todo.task }}
        <button @click="setTodoCompleted(todo, true)">Complete</button>
      </div>
    </div>

    <div>
      <b>Done:</b>
      <div v-for="todo in completedTodoList" :key="todo.id">
        {{ todo.task }}
        <button @click="removeTodo(todo)">Delete</button>
        <button @click="setTodoCompleted(todo, false)">Restore</button>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, computed, watchEffect } from "vue";

export default {
  setup() {
    const todoList = ref([
      { id: 1, task: "Configure ESLint", completed: false },
      { id: 2, task: "Learn React", completed: true },
      { id: 3, task: "Take ring to Mordor", completed: true },
    ]);

    const completedTodoList = computed(() =>
      todoList.value.filter((t) => t.completed === true)
    );
    const notCompletedTodoList = computed(() =>
      todoList.value.filter((t) => t.completed === false)
    );

    function createTodo(task) {
      todoList.value.push({ id: Math.random(), task, completed: false });
    }

    function removeTodo(todo) {
      todoList.value = todoList.filter((t) => t !== todo);
    }

    function setTodoCompleted(todo, value) {
      todo.completed = value;
    }

    function addTodo() {
      const input = document.querySelector("#new-todo");
      createTodo(input.value);
      input.value = "";
    }

    watchEffect(() => {
      console.log(todoList.value.length);
    });

    return {
      completedTodoList,
      notCompletedTodoList,
      addTodo,
      setTodoCompleted,
      removeTodo,
    };
  },
};
</script>
  • Note: I'm using the new Composition API available in Vue 3.0+ that's still in beta but should be available soon.

In Vue we can declare reactive values using the ref function from the Composition API. It returns a reactive value with a value property that tracks everytime you access it. This is so it can actually react to changes - rerun effects and recompute derived values.

We can declare derived values using the computed function. It takes a function and return the derived value - any reactive value accessed in this function is considered a dependency and if it changes, the derived value is also recomputed.

Updating state is as simple as writing to the .value prop of reactive data. Arrays can be changed directly using push, pop, splice and other array methods.

We can run effects when some data changes using watchEffect - it takes a function that runs whenever a reactive value used inside changes.

3. Svelte

Svelte uses a "radical new approach" to building UI - it's a compiler that generates code and leaves no traces of the framework at runtime.

<script>
    let todoList = [
    { id: 1, task: 'Configure ESLint', completed: false },
    { id: 2, task: 'Learn React', completed: true },
    { id: 3, task: 'Take ring to Mordor', completed: true },
  ];

    $: completedTodoList = todoList.filter(t => t.completed === true);
  $: notCompletedTodoList = todoList.filter(t => t.completed === false);

  function createTodo(task) {
    todoList = [...todoList, { id: Math.random(), task, completed: false }];
  }

  function removeTodo(todo) {
    todoList = todoList.filter(t => t !== todo);
  }

    function setTodoCompleted(todo, value) {
        todo.completed = value;
        todoList = todoList
    }

    function addTodo() {
        const input = document.querySelector('#new-todo');
        createTodo(input.value);
        input.value = '';
    }

    $: console.log(todoList.length);
</script>

<div>
    <input id="new-todo" />
    <button on:click={addTodo}>ADD</button>

    <div>
        <b>Todo:</b>
        {#each notCompletedTodoList as todo (todo.id)}
            <div>
                {todo.task}
                <button on:click={() => setTodoCompleted(todo, true)}>Complete</button>
            </div>
        {/each}
    </div>

    <div>
        <b>Done:</b>
        {#each completedTodoList as todo (todo.id)}
            <div>
                {todo.task}
                <button on:click={() => removeTodo(todo)}>Delete</button>
                <button on:click={() => setTodoCompleted(todo, false)}>Restore</button>
            </div>
        {/each}
    </div>
</div>

With Svelte, any variable declared with let can be reactive. Derived data is declared with the $: label, which is valid, albeit uncommon, Javascript sytax. Any variable referenced on the lines marked with $: is marked as a dependency of the derived variable.

The $: can also be used to trigger effects. Logging the number of todos in the list is as simple as

$: console.log(todoList.length);

Updating state can be tricky - state updates only when we write to a variable, this is why you can sometimes see code like this

todoList = todoList;

Svelte also takes pride in being fast. It's one of the fastest frameworks out there since it's a compiler that optimises itself away and leaves only pure, speedy JS in its place.

4. MobX

MobX is a state management solution and can be used with React, Vue or any UI library. I'll show its usage with React, but keep in mind it can be used with anything, even vanilla JS.

import "mobx-react-lite/batchingForReactDom";

import React from "react";
import { observable, autorun } from "mobx";
import { observer } from "mobx-react";

const state = observable({
  todoList: [
    { id: 1, task: "Configure ESLint", completed: false },
    { id: 2, task: "Learn React", completed: true },
    { id: 3, task: "Take ring to Mordor", completed: true },
  ],
  get completedTodoList() {
    return this.todoList.filter((t) => t.completed === true);
  },
  get notCompletedTodoList() {
    return this.todoList.filter((t) => t.completed === false);
  },
});

function createTodo(task) {
  state.todoList.push({ id: Math.random(), task, completed: false });
}

function removeTodo(todo) {
  state.todoList = state.todoList.filter((t) => t !== todo);
}

function setTodoCompleted(todo, value) {
  todo.completed = value;
}

function addTodo() {
  const input = document.querySelector("#new-todo");
  createTodo(input.value);
  input.value = "";
}

autorun(() => {
  console.log(state.todoList.length);
});

const App = observer(function App() {
  const { notCompletedTodoList, completedTodoList } = state;

  return (
    <div>
      <input id="new-todo" />
      <button onClick={addTodo}>ADD</button>

      <div>
        <b>Todo:</b>
        {notCompletedTodoList.map((todo) => {
          return (
            <div key={todo.id}>
              {todo.task}
              <button onClick={() => setTodoCompleted(todo, true)}>
                Complete
              </button>
            </div>
          );
        })}
      </div>

      <div>
        <b>Done:</b>
        {completedTodoList.map((todo) => {
          return (
            <div key={todo.id}>
              {todo.task}
              <button onClick={() => removeTodo(todo)}>Delete</button>
              <button onClick={() => setTodoCompleted(todo, false)}>
                Restore
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );
});

export default App;

With MobX we first pass some data to observable to make it observable. Then we can use the state just as we would use plain old JS data.

We can declare derived data by setting a getter function on the object passed to observable - this makes MobX optimise the value by caching the return value and only recomputing it when some observable value used by the getter changes.

Updating values is very simple - we can use all the common array methods like push, pop, slice etc. on observable arrays.

When we mark a React component with the observer HOC MobX will track all observable and computed values used in the component and re-render the component every time those values change. The only caveat is that MobX doesn't actually track usage, but rather it tracks data access, so you have to make sure you access the data through a property inside the observer component.

const state = observable({ count: 10 });

const count = state.count;

// This will not re-render since count no observable
// state was _accessed_ in the component
const ComponentBad = observable(() => {
  return <h1>{count}</h1>;
});

// This will re-render since count is accessed inside
const ComponentGood = observable(() => {
  return <h1>{state.count}</h1>;
});

Running effects is as simple as passing the effect to autorun. Any observable or computed values accessed in the function become the effect dependency - when they change, the effects re-runs.

5. Solid

Solid is a declarative JavaScript library for creating user interfaces. It's kinda like if React and Svelte had a baby. Here's how it looks:

import { createEffect, createMemo, createSignal } from "solid-js";
import { For } from "solid-js/dom";

export default function App() {
  const [todoList, setTodoList] = createSignal([
    { id: 1, task: "Configure ESLint", completed: false },
    { id: 2, task: "Learn React", completed: true },
    { id: 3, task: "Take ring to Mordor", completed: true },
  ]);

  const completedTodoList = createMemo(() =>
    todoList().filter((t) => t.completed === true)
  );

  const notCompletedTodoList = createMemo(() =>
    todoList().filter((t) => t.completed === false)
  );

  function createTodo(task) {
    setTodoList([...todoList(), { id: Math.random(), task, completed: false }]);
  }

  function removeTodo(todo) {
    setTodoList(todoList().filter((t) => t !== todo));
  }

  function setTodoCompleted(todo, value) {
    setTodoList(
      todoList().map((t) => {
        if (t === todo) return { ...t, completed: value };
        return t;
      })
    );
  }

  function addTodo() {
    const input = document.querySelector("#new-todo");
    createTodo(input.value);
    input.value = "";
  }

  createEffect(() => {
    console.log(todoList().length);
  });

  return (
    <div>
      <input id="new-todo" />
      <button onClick={addTodo}>ADD</button>

      <div>
        <b>Todo:</b>
        <For each={notCompletedTodoList()}>
          {(todo) => {
            return (
              <div key={todo.id}>
                {todo.task}
                <button onClick={() => setTodoCompleted(todo, true)}>
                  Complete
                </button>
              </div>
            );
          }}
        </For>
      </div>

      <div>
        <b>Done:</b>
        <For each={completedTodoList()}>
          {(todo) => {
            return (
              <div key={todo.id}>
                {todo.task}
                <button onClick={() => removeTodo(todo)}>Delete</button>
                <button onClick={() => setTodoCompleted(todo, false)}>
                  Restore
                </button>
              </div>
            );
          }}
        </For>
      </div>
    </div>
  );
}

We can create observable state using createSignal. It returns a tuple with a getter and a setter function.

To create derived data we can use createMemo. It takes a function returning the derived value, and any getter function called in the function is marked as a dependency. You know the drill, dependency changes - derived value recomputes.

Effects are created using a similar - createEffect function that also tracks dependencies, but instead of returning values it just runs some arbitrary effect.

State can be updated using the setter function returned from createSignal and calling it with the new state.

State can also be created and updated with createState which returns a more React-like tuple with the state object and a setter function.

Solid looks and reminds of React with hooks, but there are no Hook rules, or concern about stale closures.

6. Redux

Redux is a predictable state container for JavaScript apps. It's often used with React so I too went down that road.

import React from "react";
import { createSlice, configureStore } from "@reduxjs/toolkit";
import { Provider, useSelector, useDispatch } from "react-redux";

const todoSlice = createSlice({
  name: "todo",
  initialState: {
    todoList: [
      { id: 1, task: "Configure ESLint", completed: false },
      { id: 2, task: "Learn React", completed: true },
      { id: 3, task: "Take ring to Mordor", completed: true }
    ]
  },
  reducers: {
    createTodo(state, { payload: task }) {
      state.todoList.push({ id: Math.random(), task, completed: false });
    },
    removeTodo(state, { payload: id }) {
      state.todoList = state.todoList.filter((t) => t.id !== id);
    },
    setTodoCompleted(state, { payload: { id, value } }) {
      state.todoList.find((t) => t.id === id).completed = value;
    }
  }
});

const selectors = {
  completedTodoList(state) {
    return state.todoList.filter((t) => t.completed === true);
  },
  notCompletedTodoList(state) {
    return state.todoList.filter((t) => t.completed === false);
  }
};

const store = configureStore({
  reducer: todoSlice.reducer
});

// Create a cache to keep old values in.
// We use this to compare previous and next values and react only
// to parts of state we want.
const prevState = { todoList: undefined };
store.subscribe(() => {
  const state = store.getState();
  const prevTodoList = prevState.todoList;
  const todoList = state.todoList;

  if (prevTodoList !== todoList) {
    console.log(todoList.length);
  }
});

function App() {
  const dispatch = useDispatch();

  const completedTodoList = useSelector(selectors.completedTodoList);
  const notCompletedTodoList = useSelector(selectors.notCompletedTodoList);

  function addTodo() {
    const input = document.querySelector("#new-todo");
    dispatch(todoSlice.actions.createTodo(input.value));
    input.value = "";
  }

  return (
    <div>
      <input id="new-todo" />
      <button onClick={addTodo}>ADD</button>

      <div>
        <b>Todo:</b>
        {notCompletedTodoList.map((todo) => {
          return (
            <div key={todo.id}>
              {todo.task}
              <button
                onClick={() =>
                  dispatch(
                    todoSlice.actions.setTodoCompleted({
                      id: todo.id,
                      value: true
                    })
                  )
                }
              >
                Complete
              </button>
            </div>
          );
        })}
      </div>

      <div>
        <b>Done:</b>
        {completedTodoList.map((todo) => {
          return (
            <div key={todo.id}>
              {todo.task}
              <button
                onClick={() => dispatch(todoSlice.actions.removeTodo(todo.id))}
              >
                Delete
              </button>
              <button
                onClick={() =>
                  dispatch(
                    todoSlice.actions.setTodoCompleted({
                      id: todo.id,
                      value: false
                    })
                  )
                }
              >
                Restore
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default () => (
  <Provider store={store}>
    <App />
  </Provider>
);

Note that we use Redux through Redux Toolkit - the recommended approach to writting Redux with good defaults and some shortcuts to avoid writing lots of boilerplate code.

One thing you'll notice is the <Provider> component wrapping the whole app. This makes it possible for our app to access the store anywhere in the component tree. Internally it uses Reacts context API.

To define the initial state we use the createSlice function and pass it the initialState along with some reducers and the function returns the Redux store.

Reducers are usually described as pure functions that receive two arguments - the current state and an action - and return completely new state without touching the old one. However, with Redux Toolkit, when you define a reducer, the toolkit internally uses Immer so you can directly mutate the state object. The toolkit also creates an action creator that can trigger this reducer.

Derived data can be defined by creating selectors - simple functions that receive state and return the derived data.

For complex derived data Redux Toolkit exports a createSelector function that can memoize data and can be used to improve performance.

Running effects when state changes can be achieved by simply subscribing to the store using store.subscribe and passing it a function that runs whenever the state changes. If we want to subscribe only to parts of the state, we have to implement additional logic to check if that part of the state has changed. However, Redux is mostly used with React so in practice this kind of logic would most likely be implemented using Reacts own reactivity model.

State updates are simple as Redux Toolkit uses Immer behind the scenes, so we can just .push values into arrays and everything works. Only thing to remember is that in Redux you have to dispatch the actions. It's common for new devs to call an action creator without dispatch and wonder why nothing's working.

Conclusion

Different frameworks and libraries have different approaches solving the same problem.
Picking the best solution is subjective, and I can only offer my point of view, so take this with a grain of salt.

React is great. useEffect offers lots of control, derived values are simple to declare and there's lots of content online to help you out if you get stuck.
On the other hand, rules of Hooks can be confusing and it's easy to get performance issues or just getting the wrong idea and getting stuck with lots of unnecessary performance optimisations.

Vue is in my opinion the best solution in the list. It's simple, composable, fast, easy to get started with and just makes sense. The only con is that observable state has to be accessed through value which could be forgotten easily. However it's a small price to pay for all the benefits the framework offers.

Svelte is another slick solution. The $: and thing = thing syntax is a bit weird to get used to, but the performance and simplicity of Svelte is pretty great and the framework itself has a bunch of other useful features when it comes to developing UI so it's worth taking a look at.

MobX - for me personally MobX is a far better way to manage state than React Hooks. It doesn't care about the UI layer so it can be used outside the React ecosystem, and it's simple to mutate data. The only downside is that it tracks data access and not the data itself, which can be a source of bugs if you don't keep it in mind.

Solid is a relatively new project, and as such it's not used that much, but it's easy to get started if you're familiar with React. createState and createSignal are improvements over React's useState as it doesn't depend on the order of calls. But the framework is still young so the documentation can be a bit lacking. It looks promising, so we'll see what the future has in store.

Redux has been around for some time now, and it's widely used. This means that there's a lot of content online readily available for developers to pick up. It's not uncommon to hear Redux is hard to learn, and while I somewhat agree with this statement, I think Redux Toolkit makes Redux much more simple and accessible for new devs. It gives you predictable It still needs some boilerplate, but that's no problem for larger projects where it's more important to know where the updates are happening (in the reducers) than having a few lines of code less.

In the end, all approaches have its pros and cons. You have to pick the one that suits your needs the best, and don't be afraid to try new stuff.

Posted on by:

hrastnik profile

Mateo Hrastnik

@hrastnik

Tells computers what to do, and sometimes they even do it.

lloyds digital

Combining high-end technology in web, mobile and software development, also involved in service design, branding and visual communications. All in order to improve your business.

Discussion

markdown guide
 

I'd just like to say thanks for writing a nice balanced framework comparison article that emphasizes that all frameworks have pros, cons, and tradeoffs for accomplishing the same tasks! Too many articles turn it into "FrameworkA vs FrameworkB, FIGHT!".

(Since I'm a Redux maintainer and you have a MobX example in here, I'd like to request that you add a Redux example for comparison as well, preferably using syntax from our official Redux Toolkit package, but the article is excellent regardless.)

 

Hi Mark!

I've updated the article with Redux Toolkit!

 

Hi Mark! Thanks a lot for taking the time to read the article!

I'll try to add the Redux example as soon as I find the time.

 

Good article! Nice to see the same thing implemented in a bunch of the popular frameworks.

I've been playing with Svelte a bit lately too, I think it's great. You mentioned the thing = thing weirdness, and I agree it's a bit weird to get used to, but you can also write things the "immutable" way to avoid it. I noticed your removeTodo function does it this way, so maybe you already knew about this, but for setTodoCompleted you could write it this way and avoid the todoList = todoList:

function setTodoCompleted(todo, value) {
  todoList = todoList.map(t => {
    if(t === todo) {
      todo.completed = value
    }
    return t;
  })
}

Or, also, since you have the whole todo there already, there's no need to find it first, and you could do this:

function setTodoCompleted(todo, value) {
  todo.completed = value;
  todoList = todoList;
}

I tried todo.completed = value by itself and was a bit surprised it didn't work, because it seems Svelte should be able to "know" that todo came from that todoList array when it parses the template. That might be too hard with static analysis though.

Someone else already mentioned the input value binding thing, and yeah, you can shorten the code a bit by declaring a let inputValue up top and then doing <input id="new-todo" bind:value={inputValue} />. Svelte will bind that variable, and then you can just use it as normal in the addTodo function, without having to query for the input element.

 

Hi Dave!

I've updated the Svelte example with todo.completed = value. Thanks for the suggestion.

I've explained my reasoning for using querySelector to grab the input value in a reply on Silvestres comment.

 

Thank you for cool comparison article!
You should give a try for Effector. I found this solution about a year ago, and I'm crazy excited... Already used it on some production projects.

I prepared a repl, where I tried to make it similar to your example as much as possible, to implement it using effector.
share.effector.dev/vLXa3WtO
If this solution is worthy of your attention, it would be a good idea to include it in the article. Or maybe even in your production😉😃

 

I think you might want to take a look at immer-reducer, it makes working with Redux a lot simpler: github.com/esamattis/immer-reducer

Here's an example app that uses it: github.com/gamedevsam/calendar-app...

I'm a fan of Mobx myself, but if I was going to pick anything else, it would be Redux with Immer Reducer. Such elegant syntax with first class TypeScript support.

At a high level, immer-reducer turns your reducers into simple classes. Reducer actions are just simple function calls, and you mutate the state directly and under the hood immer is used to generate patches that are applied to the state that is passed to React.

The one disadvantage over Mobx is the need to still write connector components, but that can be a good thing making clear separation between stateless and stateful components depending on who you ask.

 

Writting this => const input = document.querySelector('#new-todo'); and not bind:value for Svelte, it shows us you don't have so many knowledge about Svelte.

 

Hi Silvestre! I am aware that using querySelector to get the value of the input is not idiomatic Svelte, but you'll notice I used the same approach for all frameworks/libraries.
I also didn't use v-model in Vue, didn't useState in React etc.

The idea was to use the reactivity models to expose an API to add todos, remove todos, change todo status and reactively log changes after each action.

I tried to keep the UI state out of the comparison, so I used querySelector and changed the inputs value manually.

Thanks for your reply.

 

I saw that, but being honest, the code you have with Svelte is smaller and more readable than with VUE, and even could be beter refactored. Currently the only framework which is pure reactive is Svelte. As some people say, React is a bad name for React. Cheers!

We all have our preferences, but after all, we're developers, so we just have to be above it and use whatever is the right tool for the job/management forces upon us/legacy project was written in. 😁

 

Excellent article friend, I liked you to use vue.js 3 for this example.

 

Thanks for taking the time to read it 😁.

Vue 3 is the future, after all!

 

Great, insightful article. To the point, and very well explained. Thanks for that!