Comparing the React and Vue Ecosystems with a Real-World SPA
John Datserakis | September 4th, 2018 | 9 min read
React vs Vue - a favorite talking point among developers. Many people pick a framework and stick to it, never really getting to know the library they left behind. That's mainly due to time; the only way to really get to know the ins and outs of a system is to use it, struggle with it, grow with it.
It's just not efficient to spread your focus thin across similar tools, but aren't you curious? I was.
Online you'll run into articles that attempt to compare a to-do app or the like built with Vue and React, but rarely is a project so simple. With real applications, we're worried about routing, state management, plugin compatibility, etc.
My question was not what are the differences between the Vue and React core libraries, but rather, how does building a real-world app compare in these frameworks? Which ecosystem of tools provides a better development experience for me when I'm building an SPA?
The Apps
I've been using Vue for about two years and developing for about eight. When I first started with Vue I decided I would learn it "out in the open" by open-sourcing a simple notes app that had fuller features like user-authentication using JWT and full CRUD note actions. This was partnered with a backend node API built using Koa.
Even though I don't really have a specific need to change frameworks, I figured it would be a good idea to learn React. So I remade my koa-vue-notes app in React and open-sourced it as well. I figured the experience would broaden my understanding of JavaScript at the very least, and perhaps I'd find a new favorite tool.
Here's the homepage of the app. The top is React, the bottom is Vue:
Although Bootstrap usage in my apps is becoming less and less, I usually opt to use the new Navbar component in Bootstrap 4. To do this in Vue, I find Bootstrap-Vue to be the best choice for Bootstrap 4 implementation. For React, my research and testing lead me to reactstrap.
One thing to mention is that I didn't end up using the Bootstrap grid in React - instead, I opted for grid-styled to better match my
styled-components
usage - more on that later.
In the app, you can signup/login/forgot/reset
a user, and create/read/edit/delete
notes for that user. Login with demousername
and demopassword
if you don't care to sign up.
Source Folder Comparison
Initial Impressions
One thing becomes clear right away when you work with React: you're going to be working very close to the JavaScript.
I'm drawn to minimalist tendencies and I look to cut junk where I don't need it. So it should make sense that React's raw nature would appeal to me. After dealing with Vue for a few years and then working with React I quickly felt like Michael looking at Toby thinking, "Why are you the way you are?".
React-Router vs Vue-Router
React-Router is a heavily used router system for React. Its speed was great, but I ran into some interesting issues during my usage. The basic set up was straightforward, although I'm not a big fan of declaring the routes right in the HTML like React-Router v4 requires you to do (unlike previous versions of React-Router).
As I continued to flesh out my routes I ran into an issue locking users out of pages they shouldn't have access to. A basic example of this is a user trying to access an account
type page when not logged in. It took a lot of trial and error and research hours to come up with a final solution with React-Router.
In the end, I wasn't happy with the code clarity or the ease-of-use I had implementing such basic functionality. The code for locking users out of a page is below:
...
<Route path="/dashboard" render={() => (
this.props.user) ? <Dashboard /> : <Redirect to="/" />
)}/>
<Route path="/createNote" render={() => (
(this.props.user) ? <CreateNote /> : <Redirect to="/" />
)}/>
<Route path="/editNote" render={() => (
(this.props.user) ? <EditNote /> : <Redirect to="/" />
)}/>
...
Vue-Router is Vue's first-party routing library. I really like the way you can add additional info to your route definitions right in your route declaration file. Take a look at how I locked users out with Vue-Router using the requiresAuth
property on the route definition and a check for truthiness in my router.beforeEach
function:
...
{
path: '/account',
component: Account,
name: 'account',
meta: {title: 'Account', requiresAuth: true}
},
{
path: '/createNote',
component: CreateNote,
name: 'createNote',
meta: {title: 'Create Note', requiresAuth: true}
},
{
path: '/editNote',
component: EditNote,
name: 'editNote',
meta: {title: 'Edit Note', requiresAuth: true}
}
...
router.beforeEach((to, from, next) => {
...
// If the user's not logged in do not allow into protected pages.
if (to.meta.requiresAuth && !router.app.$options.store.getters['user/user']) {
next({name: 'home'})
}
next()
})
Now, looking at the Vue code, it seems a bit more verbose, but that's how it's laid out in the documentation, and it was trivial to set up in the app. I can't say the same about the React code; it took me a few hours to nail down that solution. Something as essential to an app as locking users out of pages they shouldn't be seeing yet... that shouldn't take a whole night to solidify.
And then, as I was looking to grab some data from the URL for the Edit page, I found that React-Router had removed that ability in the most recent version. I found that... disappointing. I guess I understand the reasoning: query-string data comes in all different shapes and sizes, but shucks, to not be able to grab a parameter from the URL, that felt a bit extreme. I had to download the qs library to properly parse the URL, which had its own procedural quirks. Full discussion here.
All in all, that took an additional hour to sort out. Not the biggest issue, but a stark difference from my experience with Vue-Router which was: search it in the docs and implement the solution in the code. That's not to say everything is all butterflies with Vue, but for some reason, it just seemed like I ran into more roadblocks than I was expecting with React.
Redux vs Vuex
Redux is React's most popular central data store based on the Flux pattern. If you're not familiar with Flux, it's a design pattern that basically revolves around uni-directional data flow carried out by dispatching actions from within the app. In other words, it keeps everything in order when trying to access or manipulate data from all your different components.
Here's an example from our Redux store files where we create a note using actions
and a reducer
:
export const ADD_NOTE_TO_STACK = 'notes:addNoteToStack'
export const addNoteToStack = (note) => {
return {
type: ADD_NOTE_TO_STACK,
payload: { notes: note }
}
}
// Our action
export const createNote = (data) => {
return async (dispatch, getState) => {
try {
setAuthorizationHeader(getState().user.accessToken)
let createResult = await axios.post('notes', data)
let insertId = createResult.data.id[0]
let getSingleNoteResult = await dispatch(getNote(insertId))
await dispatch({ type: ADD_NOTE_TO_STACK, payload: getSingleNoteResult.data})
} catch (error) {
throw new Error(error)
}
}
}
// Our reducer
const notesReducer = (state = {notes: []}, action) => {
switch (action.type) {
case 'notes:addNoteToStack':
return {
...state,
notes: [action.payload].concat(state.notes)
}
...
}
}
// Calling it from a component
await this.props.createNote({
title: this.state.title,
content: this.state.content
})
Basically, the idea is that you dispatch actions
to trigger reducers
that safely manipulate the store's data. In this way, each component can safely read and react to changes in data.
Vuex is the equivalent of Redux in the Vue world. Both libraries have really great first-party support in this area. Instead of reducers
, Vuex uses mutations
to safely update the store's data. Other than some naming differences, both libraries are very similar. Here's how I implemented the same functionality in the Vue app in src/store/note.js
(both examples truncated a bit of course):
const ADD_NOTE_TO_STACK = 'ADD_NOTE_TO_STACK'
const note = {
state: {
notes: []
},
mutations: {
ADD_NOTE_TO_STACK (state, note) {
state.notes.unshift(note)
}
},
getters: {
notes (state) {
return state.notes
}
},
actions: {
// API Calls
async createNote ({ dispatch, commit, getters, rootGetters }, data) {
try {
setAuthorizationHeader(rootGetters['user/accessToken'])
return await axios.post('notes', {title: data.title, content: data.content})
} catch (error) {
throw new Error(error)
}
},
// Only Mutations
async addNoteToStack ({ dispatch, commit, getters, rootGetters }, note) {
try {
commit(ADD_NOTE_TO_STACK, note)
} catch (error) {
throw new Error(error)
}
}
}
}
// Calling it from a component
const responseCreate = await this.$store.dispatch('note/createNote', this.note)
await this.$store.dispatch('note/addNoteToStack', responseCreate)
Honestly, I find Redux to be a useful and powerful Flux-inspired store library for React. My problem with it is the extra boilerplate. Sure, now that it's sorted out it seems clear and simple, but from my experience, it was tough to find and implement clear, concise code in React as a newbie to the library.
For example, having to learn and install the redux-thunk library in order to dispatch actions from other actions was a turn-off. Of course, I spent another few hours researching whether I should be using redux-saga or redux-observable instead of redux-thunk. That's when my brain made a noise that could be described as a thunk.
This was a running theme for this project. Contrast that with the Vuex integration - I specifically remember thinking, "Wow, is that it?" when getting it hooked up the first time, and that was before I even had experience with the Flux design pattern.
Rendering
The strangest thing for me in React was the render function. In Vue, it's just so easy to loop over data and spit out elements or show/hide data based on state/store variables. In React it felt pretty strange to have to create my notes loop outside of the render.
In Vue, if you want to show or hide something, just use:
<div v-if="myVariable">Lorem...</div>
and it'll be based off your myVariable
truthiness. In React, it seems like you have to do:
{this.state.myVariable &&
<div>Lorem...</div>
}
It's a little more verbose and doesn't support that instant-helper looping that Vue can manage with v-for
. But of course, after I familiarized myself with how to do those normal little things, it didn't seem too odd. It's like, ok, whatever, that's just how you do that in React. But, something needs to be said for the ease of use that Vue brings when accessing data in your actual layout. I felt it right away. It seems that little helper functions aren't really React's cup of tea.
Styled-Components
One of my favorite parts of this project? Styled-Components. I really love the encapsulation they bring. Of course, in Vue, you can pin the scoped
property in your component's <style></style>
section and basically do the same thing.
There was just something really slick about how each component became its own little world. It's a touch tricky with the passing of props, but after ironing out some of the details it was a joy to use. I remember a user's comment somewhere that summed it up perfectly, "It makes you look forward to styling your components".
I think the big reason React user's really dig it is because previously, styling components was a bit cumbersome. I guess we're a bit spoiled with the whole Single File Components world in Vue. This project makes me appreciate Single File Components that much more - truly a killer feature.
Create-React-App vs Vue-CLI
I really liked create-react-app. While I'm a huge fan of vue-cli, create-react-app is a worthy competitor for sure. I recommend all users set a Webpack instance from scratch to learn the details. But when you're looking for something solid for production, I do suggest to use the first-party scaffolding tools.
Dev Tools
One other thing: the Redux and React dev tools are definitely not as nice as the Vue tools, from the style and colors to having to open a huge tree of elements just to see a component's state, it made it a bit tough to get a view of my app's variables.
I may be missing something here though or using a version that isn't the current community standard. The Vue tools are seriously awesome, and honestly really well designed and easy on the eyes. Considering how much time you spend using these tools, these small details are important.
Wrap Up
All things being equal, I'm glad I took the time to learn React. I know I'm still shit at using it and at programming in general, but at least now I've worked through some of the tougher parts and familiarized myself with its concepts. Also, I plan to check out React Native perhaps for future mobile apps - this experience will come in handy I'm sure.
I could go on and on about the small details. this article is just a small drop in the ocean that is a Vue/React comparison. Check out the app, it's got lots of little comments and tips to help you through.
Bottom line: I'm going to start my next project using Vue. React was manageable, but it just seems like there are fewer batteries included. That can be a plus at first, but after you sorta get the idea of how things work, it seems like you're just writing more code then you need to.
About John Datserakis
Hey there - my name's John Datserakis. I'm a Lead Full-Stack Developer based out of Boston's North Shore. I'm interested in solving problems and taking projects to market. I alternate between rewatching The Office and Parks and Rec on a monthly basis. Let's chop it up.
Get in touch:
Click to load comments...