Vue vs. React — code differences and the app-building process

Author Davor Tvorić
Category Development
Date Sep 07, 2020
10 min read

When it comes to building apps in JavaScript, my go-to frameworks are Vue and React. They have their similarities, but also a bit of differences when it comes to writing code, so let’s explore some of them.

This article is a continuation of my previous article about my journey to mastering React. I thought it would be a good idea to make a comparison between Vue and React, but in a more specific way. Rather than analyzing the high-level differences, we’ll be taking a look at the code and the overall development experience in both technologies.

For this article, I decided to create the same, although a rather simple, application in Vue and React. My reasoning behind this is that I haven’t found that many examples of a comparison made on a simpler application. And it made no sense to recreate a larger application twice, especially since a great example already exists.

Overview

Here is an example of a screen you might see in the application. The search bar is connected to the PokeAPI and it allows you to search for Pokemon by their name or ID. After you’ve searched for a Pokemon, you can see its stats and also add it to a list of your favourite Pokemon. Afterwards, you can remove a Pokemon from the said list.

The project I made represents a more realistic scenario where I’ve used a UI library and made some simple API requests, much like you would do in a real project. This way, I was offered a much broader palette of functionalities to showcase. I have also used Typescript to mimic the workflow in Bornfight.

Both projects were set up in the same way, so we could clearly see the differences without worrying about different implementations between the libraries. The project’s repository is located on Github, so you can check it out for yourself if you’d like. I’ve also tried to name all of the variables and components the same, but some inconsistencies might creep up. It might be a good idea to mention that a lot of the conclusions will be my own opinions which might differ from yours. I urge you to do something similar by yourself because it’s really interesting to dissect an idea this way!

Setup

The setup went on pretty much without any difference between the two, although Vue’s CLI made it a bit neater. The only thing that had to be installed additionally for React was node-sass to allow me to use SASS inside the project, but that’s about it. I’d also note that I used Vue 2 instead of Vue 3, even though Vue 3 and current React might be a bit more similar.

This was the Vue setup with the CLI:

And this was the same process for React:

I’ve additionally installed axios and the Ant Design implementation for each.

Structure

The structure, in the end, was pretty much the same. I did make a few more folders for the React application because they also hold the .scss files.

On the left is the React app (just the /src). As you can see most of it is the same as the Vue app, save for some default stuff generated by each app, ie. typings and .env. The only larger difference is that I made the interfaces folders for each component for React since they also hold the prop types.

Implementation

The next logical step to this would be to check out the code, so we’re going to do just that. I’ll try to follow the hierarchy of the app to make the comparisons. The App components are pretty much the same, so we’re gonna jump straight in the Search components.

First of all, the state management is completely different in Vue 2 and React.

const [query, setQuery] = useState("");
const [currentPokemon, setCurrentPokemon] = useState(null);
const [favouritePokemon, setFavouritePokemon] = useState(new Set<Pokemon>());
data() {
 return {
   currentPokemon: null,
   favouritePokemon: []
 };
}

We do have an additional property (query) on the React side, as well as a much more suitable data type for our list of favorite Pokemon. From here on out, we’ll see that React has a bit of a better symbiosis with Typescript. Declaring prop and data types was much easier in React than in Vue. But I should mention that these issues are mostly fixed with Vue 3!

Alongside our list of favourite Pokemon, we also have our currently searched Pokemon object, as well as our search query. The initial value of our current Pokemon is null, so to change that we must fetch it from our API.

In React, we’ll use the useCallback function to do that.

const fetchPokemon = useCallback(async () => {
   let pokemon;
   try {
       pokemon = await axios(`https://pokeapi.co/api/v2/pokemon/${query}`);
   } catch (e) {
       notification.error({
           message: "Pokemon doesn't exist",
           description: "A Pokemon by this ID or name does not exist."
       });
       return;
   }

   setCurrentPokemon(pokemon.data);

}, [query]);

const searchPokemonByIdOrName = (idOrName: string) => {
   setQuery(idOrName);
}

useEffect(() => {
   if (query !== "") {
       fetchPokemon();
   }
}, [fetchPokemon, query]);

It’s nothing that spectacular, to be honest. We use axios to get the data, so if the request is successful, we set the data with setCurrentPokemon() function and pass the Pokemon object. If you’re not familiar with this procedure, I recommend reading about it in the React docs. If it’s not successful, an error message is displayed to the user using the Ant Design’s notification system.

As you can see, the query is listed in the dependency array. That way React knows which data must be tracked to check for changes. We must also use useEffect to trigger fetching Pokemon. useEffect does not support async functions, but we can call async functions inside it. So, every time a query (or fetchPokemon) changes, fetchPokemon is called. To make it all work, we use the searchPokemonByIdOrName() to simply change the query and trigger everything else.

The same thing in Vue looks something like this.

async searchPokemonByIdOrName(searchQuery: string | number) {
 let pokemon;
 try {
   pokemon = await PokeService.findPokemonByIdOrName(searchQuery);
 } catch (e) {
   this.$notification.error({
     message: "Pokemon doesn't exist",
     description: "A Pokemon by this ID or name does not exist."
   });
   return;
 }
 this.currentPokemon = pokemon;
}

The functionality is basically the same, although I have extracted the API call in a separate class to prevent polluting the component. In hindsight, this probably wasn’t necessary and would work the same as in the React app.

Just for reference, I’ll show you the PokeService class so you can see no funny business is happening inside.

export class PokeService {
 static async findPokemonByIdOrName(idOrName: string | number) {
   let pokemon;
   try {
     pokemon = (await Vue.axios.get(`/pokemon/${idOrName}`)) as AxiosResponse;
   } catch (e) {
     return Promise.reject(e);
   }

   return Promise.resolve(pokemon.data);
 }
}

Hopping back to our component, the whole process is much simpler in Vue. We can just assign the Pokemon object to our data property and Vue handles the rest for us. To be fair, I handled the Vue part in a bit more declarative way, so the amount of code is smaller in Vue. Vue does a lot more for us under the hood than React. This is great for the majority of the cases, but sometimes we need the low-level control React offers us.

Adding and removing favorites is pretty straightforward, but there are some conversions happening to the array when filtering. That is because filtering isn’t supported on a set.

You can see this in the code for React.

const addToFavourites = (pokemon: Pokemon)  => {
   setFavouritePokemon(new Set<Pokemon>([...Array.from(favouritePokemon), pokemon]))
}

const removeFromFavourites = (id: number) => {
   const filteredFavouritePokemon = Array.from(favouritePokemon).filter((pokemon: Pokemon) => {
       return pokemon.id !== id;
   })
   setFavouritePokemon(new Set(filteredFavouritePokemon));
}

In Vue, functions are named a bit differently, but they do the same thing. They do it in a different way because favouritePokemon in Vue is declared as an array, instead of a set. Other than this, I’d say this part is pretty much the same since it represents our business logic.

onAddToFavourites(pokemon: Pokemon) {
 const pokemonSet: Set<Pokemon> = new Set(this.favouritePokemon);
 pokemonSet.add(pokemon);
 this.favouritePokemon = Array.from(pokemonSet);
},
onRemoveFromFavourites(id: number) {
 this.favouritePokemon = this.favouritePokemon.filter(
   (pokemon: Pokemon) => pokemon.id !== id
 );
}

I used a set in both cases to prevent the user from having the same favourite Pokemon multiple times.

Now we’re going to take a look at the templates of these components.

The first one is for React.

<div>
   <SearchBar onSearch={searchPokemonByIdOrName} />
   <div className={styles.wrapper}>
       <PokemonDetails pokemon={currentPokemon} addFavouritePokemon={addToFavourites}/>
       <PokemonList favouritePokemon={favouritePokemon} removeFavourite={removeFromFavourites}/>
   </div>
</div>

And the other one is for Vue.

<template>
 <div>
   <SearchBar :on-search="searchPokemonByIdOrName"></SearchBar>
   <div class="wrapper">
     <PokemonDetails
       :pokemon="currentPokemon"
       @add-to-favourites="onAddToFavourites"
     ></PokemonDetails>
     <PokemonList
       :favourite-pokemon="favouritePokemon"
       @remove-favourite="onRemoveFromFavourites"
     ></PokemonList>
   </div>
 </div>
</template>

As you can see, there isn’t much difference between the two. It’s a bit of a different syntax for passing down props, but other than that it’s kind of the same.

There is this weird @ symbol dotted around in the Vue template. I will explain this in a bit, but this is the two-way component communication in Vue, between a parent and a child component. We can send props down to the child, but the child can inform the parent about some changes using events. React doesn’t have its own version of events since it promotes a unidirectional data flow between components.

I will briefly show you the other components, but it won’t be as detailed as the Search component since we covered most of it.

Search bar

To finally see some differences between React and Vue, here are the SearchBar components, respectively.

export const SearchBar: FunctionComponent<SearchBarProps> = (props) => {

   const {Search} = Input;

   return (
       <Search className={styles.pokemonSearch} placeholder="Pikachu, Mewtwo or 5" onSearch={props.onSearch} enterButton />
   )
}

Notice the type declaration in the example above for React. We can easily declare an interface to set the types for our props using Typescript.

The same is not that easy to achieve in Vue. In the example below we must import the PropType and then declare our type. It’s not much of a difference, but I like how React handled this.

<template>
 <a-input-search
   class="pokemon-search"
   placeholder="Pikachu, Mewtwo or 5"
   enter-button
   @search="onSearch"
 />
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
 name: "SearchBar",
 props: {
   onSearch: {
    type: Function as PropType<(searchQuery: string) => void>,
    required: true
   }
 }
});
</script>

I use the same Ant Design’s components for searching. It wasn’t that hard switching between the two.

Pokemon details

The last components we’re going to take a look at are PokemonDetails.

export const PokemonDetails: FunctionComponent<PokemonDetailsProps> = ({pokemon, addFavouritePokemon}) => {
   const renderTypes = (types: PokemonType[]) => {
       return types.map((type: PokemonType) => {
           return (
               <Tag key={type.type.name} className={styles.typeTag} color="blue">{type.type.name}</Tag>
           )
       })
   }

   if (pokemon == null) {
       return (
           <div>
               Please search for a Pokemon.
           </div>
       )
   }
.
.
.

The normal React flow to mutate some data in the parent component from the child components is simply to pass a function that does that in the parent. After this, we can treat it like any other function and assign it to a click listener, for instance. I found it interesting how conditional rendering is handled here. We can make an early return and return any component (or null) to handle some special case.

<div class="pokemon-wrapper" v-if="pokemon">
.
.
.
</div>
<div v-else>
 Please search for a Pokemon.
</div>

In Vue, we can use v-if/v-else directives which append or eject HTML elements from the DOM depending on the condition. In the end, it’s the same, but we can see the multiple ways you can handle the same problem.

Remember the weird @ syntax?

onAddToFavourites() {
 this.$emit("add-to-favourites", this.pokemon);
}

This is how we emit events and data from a child to parent. Of course, we could have gone the same way as in React (and how we did it in the SearchBar.vue component), but this is something that is widely used in Vue.

The final difference I want to show you is handling properties that must be altered before they’re shown to the user. We saw this in React, a couple of examples above, but I haven’t commented anything about it. Try to see the difference for yourself with the same idea in Vue.

computed: {
 formattedTypes() {
   if (this.pokemon == null) {
     return [];
   }
   return this.pokemon.types.map((type: PokemonType) => type.type.name);
 }
}

Can you make the connection?
In Vue, we use a computed property to do some additional calculations or formatting. Anything declared in the computed object will also be recalculated after any of the data inside changes. This specific example could have been done with a method, as well.

In React we created a renderTypes function which does the same thing as in Vue, but immediately returns an array of components. We can directly reference this in the template. In Vue, we just handle the data and declare the HTML structure in the template.

<td>
 <a-tag
   class="type-tag"
   color="blue"
   :key="type"
   v-for="type in formattedTypes"
 >
   {{ type }}
 </a-tag>
</td>

To render an array, once again we use another Vue directive from our toolbelt. I love this solution because we just handle the data in the Javascript part and the HTML layout is done in the template.

Summary

There is probably a lot more to uncover here and I’ve probably skipped a few explanations, but I hope I’ve helped you out even by a tiny bit.

My takeaway is that both React and Vue offer a lot and can be used to make amazing things. I would like to point out that writing Vue felt a bit more natural, if that’s the right word for it. Most of the things initially made sense, while for React some things needed thinking through. For instance, rendering an array of data. I’m not saying that React’s method is worse, just that Vue’s made a bit more sense when learning. On the other side, React did offer a much better Typescript support and everything just worked as you expected it would.

I still don’t think there is a clear winner here and your choice in technology still depends on the needs of your project. I don’t think it’s good putting all the eggs in one basket, so it’s not a bad idea to learn the basics of both!

Some stuff hasn’t been shown in this article, but I’ll link the repo again, so you can check out how everything works in tandem. If you’ve got some time to spare, try to make something like this on your own because it’s a really fun exercise. If you do something like that, I would love to see it, as well as any comments on my current code. I’m always willing to improve my skills!

Subscribe to our newsletter

Subscribe now and get our top news once a month.

100% development, design & strategy.

100% spam-free.

You're now a part of our club!

We got your e-mail address and you'll get our next newsletter!