So you can see that we have passed the same data into both, but the structure is a bit different.

In Vue, you would typically place all of your mutable data for a component inside of a data() function that returns an object with your data inside of it (as we can see from the image on the right).

With React — or at least since 2019 — we would typically handle state through a series of Hooks. These might look a bit strange at first if you haven’t seen this type of concept before. Basically, it works as follows:

Let’s say we want to create a list of todos. We would likely need to create a variable called list and it would likely take an array of either strings or maybe objects (if say we want to give each todo string an ID and maybe some other things. We would set this up by writing const [list, setList] = useState([]) . Here we are using what React calls a Hook — called useState. This basically lets us keep local state within our components.

Also, you may have noticed that we passed in an empty array [] inside of useState() . What we put inside there is what we want list to initially be set to, which in our case, we want to be an empty array. However, you will see from the image above that we passed in some data inside of the array, which ends up being the initialised data for list . Wondering what setList does? There will be more on this later!

So how would we reference our mutable data in our app?

Well, let’s say that we have some piece of data called name that has been assigned a value of ‘Sunil’ .

In Vue, this would be sitting inside of the data() object and would have been called name: ‘Sunil' . In our app, we would reference this by calling this.name . We can also go about updating this by calling this.name = ‘John’ . This would change my name to John. I’m not sure how I feel about being called John, but hey ho, things happen! 😅

In React, as we have our smaller pieces of state that we created with useState() , it is likely that we would have created something along the lines of const [name, setName] = useState('Sunil') . In our app, we would reference the same piece of data by calling simply calling name . Now the key difference here is that we cannot simply write name = ‘John’ , because React has restrictions in place to prevent this kind of easy, care-free mutation-making. So in React, we would write setName('John') . This is where the setName bit comes into play. Basically, in const [name, setName] = useState('Sunil') , it creates two variables, one which becomes const name = 'Sunil' , while the second const setName is assigned a function that enables name to be recreated with a new value.

Effectively React and Vue are doing the same thing here, which is creating data that can be updated. Vue essentially combines its own version of name and setName by default whenever a piece of data gets updated. So in short, React requires that you call setName() with the value inside in order to update state, Vue makes an assumption that you’d want to do this if you were ever trying to update values inside the data object. So Why does React even bother with separating the value from the function, and why is useState() even needed? Let’s hand this over to Revanth Kumar for an explanation:

“This is because React wants to re-run certain life cycle hooks whenever state changes. It would know that the state has changed when you call the useState function. If you directly mutated state, React would have to do a lot more work to keep track of changes and what lifecycle hooks to run etc. So to make it simple React uses useState.”

Bean knew best.

Now that we have mutations out of the way, let’s get into the nitty, gritty by looking at how we would go about adding new items to both of our To Do Apps.

How do we create new To Do Items?

React:

const createNewToDoItem = () => {

const newId = Math.max(...list.map((t) => t.id)) + 1

const newToDo = { id: newId, text: toDo }; setList([...list, newToDo]);

setToDo("");

};

How did React do that?

In React, our input field has an attribute on it called value. This value gets automatically updated every time its value changes through what is known as an onChange event listener. The JSX (which is basically a variant of HTML), looks like this:

<input type="text"

value={toDo}

onChange={handleInput}/>

So every time the value is changed, it updates state. The handleInput function looks like this:

const handleInput = (e) => {

setToDo(e.target.value);

};

Now, whenever a user presses the + button on the page to add a new item, the createNewToDoItem function is triggered. Let’s take a look at that function again to break down what is going on:

const createNewToDoItem = () => {

const newId = Math.max(...list.map((t) => t.id)) + 1

const newToDo = { id: newId, text: toDo }; setList([...list, newToDo]);

setToDo("");

};

Essentially the newId function is basically creating a new ID that we will give to our new toDo item. The newToDo variable is an object that takes that has an id key that is given the value from newId . It also has a text key which takes the value from toDo as its value. That is the same toDo that was being updated whenever the input value changed.

We then run out setList function and we pass in an array that includes our entire list as well as the newly created newToDo .

If the ...list , bit seems strange, the three dots at the beginning is something known as a spread operator, which basically passes in all of the values from the list but as separate items, rather than simply passing in an entire array of items as an array. Confused? If so, I highly recommend reading up on spread because it’s great!

Anyway, finally we run setToDo() and pass in an empty string. This is so that our input value is empty, ready for new toDos to be typed in.

Vue:

createNewToDoItem() {

const newId = Math.max(...this.list.map(t => t.id)) + 1;



this.list.push({ id: newId, text: this.todo });

this.todo = "";

}

How did Vue do that?

In Vue, our input field has a handle on it called v-model. This allows us to do something known as two-way binding. Let’s just quickly look at our input field, then we’ll explain what is going on:

<input type="text" v-model="todo"/>

V-Model ties the input of this field to a key we have in our data object called toDoItem. When the page loads, we have toDoItem set to an empty string, as such: todo: ‘’. If this had some data already in there, such as todo: ‘add some text here’, our input field would load with add some text here already inside the input field. Anyway, going back to having it as an empty string, whatever text we type inside the input field gets bound to the value for todo. This is effectively two-way binding (the input field can update the data object and the data object can update the input field).

So looking back at the createNewToDoItem() code block from earlier, we see that we push the contents of todo into the list array and then update todo to an empty string.

We also used the same newId() function as used in the React example.

How do we delete from the list?

React:

const deleteItem = (item) => {

setList(list.filter((todo) => todo !== item));

};

How did React do that?

So whilst the deleteItem() function is located inside ToDo.js, I was very easily able to make reference to it inside ToDoItem.js by firstly, passing the deleteItem() function as a prop on <ToDoItem/> as such:

<ToDoItem deleteItem={deleteItem}/>

This firstly passes the function down to make it accessible to the child. Then, inside the ToDoItem component, we do the following:

<button className="ToDoItem-Delete" onClick={() => deleteItem(item)}> - </button>

All I had to do to reference a function that sat inside the parent component was to reference props.deleteItem. Now you may have noticed that in the code example, we just wrote deleteItem instead of props.deleteItem . This is because we used a technique known as destructuring which allows us to take parts of the props object and assign them to variables. So in our ToDoItem.js file, we have the following:

const ToDoItem = (props) => {

const { item, deleteItem } = props;

}

This created two variables for us, one called item , which gets assigned the same value as props.item , and deleteItem , which gets assigned the value from props.deleteItem . We could have avoided this whole destructuring thing by simply using props.item and props.deleteItem , but I thought it was worth mentioning!

Vue:

onDeleteItem(item){

this.list = this.list.filter(todo => todo !== item);

}

How did Vue do that?

A slightly different approach is required in Vue. We essentially have to do three things here:

Firstly, on the element we want to call the function:

<div class=”ToDoItem-Delete” @click=”deleteItem(item)”>-</div>

Then we have to create an emit function as a method inside the child component (in this case, ToDoItem.vue), which looks like this:

deleteItem(item) {

this.$emit('delete', item)

}

Along with this, you’ll notice that we actually reference a function when we add ToDoItem.vue inside of ToDo.vue:

<ToDoItem v-for="todo in list"

:todo="todo"

@delete="onDeleteItem" // <-- this :)

:key="todo.id" />

This is what is known as a custom event-listener. It listens out for any occasion where an emit is triggered with the string of ‘delete’. If it hears this, it triggers a function called onDeleteItem. This function sits inside of ToDo.vue, rather than ToDoItem.vue. This function, as listed earlier, simply filters the todo array inside the data object to remove the item that was clicked on.

It’s also worth noting here that in the Vue example, I could have simply written the $emit part inside of the @click listener, as such:

<div class=”ToDoItem-Delete” @click=”$emit(‘delete’, item)”>-</div>

This would have reduced the number of steps down from 3 to 2, and this is simply down to personal preference.

In short, child components in React will have access to parent functions via props (providing you are passing props down, which is fairly standard practice and you’ll come across this loads of times in other React examples), whilst in Vue, you have to emit events from the child that will usually be collected inside the parent component.

How do we pass event listeners?

React:

Event listeners for simple things such as click events are straight forward. Here is an example of how we created a click event for a button that creates a new ToDo item:

<button className=”ToDo-Add” onClick={createNewToDoItem}>+</div>.

Super easy here and pretty much looks like how we would handle an in-line onClick with vanilla JS. As mentioned in the Vue section, it took a little bit longer to set up an event listener to handle whenever the enter button was pressed. This essentially required an onKeyPress event to be handled by the input tag, as such:

<input type=”text” onKeyPress={handleKeyPress}/>.

This function essentially triggered the createNewToDoItem function whenever it recognised that the ‘enter’ key had been pressed, as such:

handleKeyPress = (e) => {

if (e.key === ‘Enter’) {

createNewToDoItem();

}

};

Vue:

In Vue it is super straight-forward. We simply use the @ symbol, and then the type of event-listener we want to do. So for example, to add a click event listener, we could write the following:

<button class=”ToDo-Add” @click=”createNewToDoItem()”>+</div>

Note: @click is actually shorthand for writing v-on:click. The cool thing with Vue event listeners is that there are also a bunch of things that you can chain on to them, such as .once which prevents the event listener from being triggered more than once. There are also a bunch of shortcuts when it comes to writing specific event listeners for handling key strokes. I found that it took quite a bit longer to create an event listener in React to create new ToDo items whenever the enter button was pressed. In Vue, I was able to simply write:

<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>

How do we pass data through to a child component?

React:

In react, we pass props onto the child component at the point where it is created. Such as:

<ToDoItem key={key.id} item={todo} />

Here we see two props passed to the ToDoItem component. From this point on, we can now reference them in the child component via this.props. So to access the item.todo prop, we simply call props.item.

Vue:

In Vue, we pass props onto the child component at the point where it is created. Such as:

<ToDoItem v-for="item in list"

:item="item"

@delete="onDeleteItem"

:key="item.id" />

Once this is done, we then pass them into the props array in the child component, as such: props: [ ‘todo’ ]. These can then be referenced in the child by their name — so in our case, ‘todo’.

How do we emit data back to a parent component?

React:

We firstly pass the function down to the child component by referencing it as a prop in the place where we call the child component. We then add the call to function on the child by whatever means, such as an onClick, by referencing props.whateverTheFunctionIsCalled — or whateverTheFunctionIsCalled if we have used destructuring. This will then trigger the function that sits in the parent component. We can see an example of this entire process in the section ‘How do we delete from the list’.

Vue:

In our child component, we simply write a function that emits a value back to the parent function. In our parent component, we write a function that listens for when that value is emitted, which can then trigger a function call. We can see an example of this entire process in the section ‘How do we delete from the list’.

And there we have it! 🎉

We’ve looked at how we add, remove and change data, pass data in the form of props from parent to child, and send data from the child to the parent in the form of event listeners. There are, of course, lots of other little differences and quirks between React and Vue, but hopefully the contents of this article has helped to serve as a bit of a foundation for understanding how both frameworks handle stuff 🤓. And if you have enjoyed reading this, be sure to show your love by leaving some claps — hint, you can leave up to 50!

If you’re interested in forking the styles used in this article and want to make your own equivalent piece, please feel free to do so! 👍

Why didn’t you use the Vue Composition API?

There is actually an updated version of this article that uses it! The latest edition be read here! But before you click the link, be sure to leave some claps on this article if you enjoyed reading it, as they help to support the work that we are doing.

Github links to both apps:

Vue ToDo: https://github.com/sunil-sandhu/vue-todo-2019

React ToDo: https://github.com/sunil-sandhu/react-todo-2019

I recently spoke about this article at London Web Performance

Watch the talk here! https://www.youtube.com/watch?v=dnNF8szmxXg

The 2020 version of this article

https://sunilsandhu.com/posts/i-created-the-exact-same-app-in-react-and-vue-2020-edition

The 2018 version of this article

https://medium.com/javascript-in-plain-english/i-created-the-exact-same-app-in-react-and-vue-here-are-the-differences-e9a1ae8077fd

Curious to see how the same app would work with React and Redux? Read more about it on the link below:

https://medium.com/javascript-in-plain-english/i-created-the-exact-same-app-with-react-and-redux-here-are-the-differences-6d8d5fb98222

Have you heard about Svelte? Well we also created the same app in Svelte too!

https://medium.com/javascript-in-plain-english/i-created-the-exact-same-app-in-react-and-svelte-here-are-the-differences-c0bd2cc9b3f8

Translations

Japanese

Korean

If you would like to translate this article into another language, please go ahead and do so — let me know when it is complete so that I can add it to the list of translations above.