React Hooks API vs Vue Composition API, as explored through useState
Tags: vue, react, webdev, javascript
Published: 2020-08-19 04:26:10

You've read plenty of high-level, abstract articles about React vs Vue, about Hooks API vs Composition API.

This article is different.

This is exploring *one specific feature*, React's `useState` hook, and seeing how we can accomplish the equivalent functionality with Vue's Composition API.

While we're exploring, we're going to uncover what I believe are two of the core philosophical differences between React and Vue, and how that effects every single facet of their APIs.

I'm going to offer my opinion on how each framework stacks up, but I'm also going to give you side-by-side code comparisons so you can make your own decision.  

*This article presumes familiarity with either React or Vue.  If you want something for a team completely new to frontend development, [sign up for my newsletter](https://www.vuetraining.net/react-vs-vue-guide-signup) and you'll be the first to know when that's ready.*

# useState and the Click Counter

We're going to start with the basic example shown in the `useState` documentation.  You click a button which adds 1 to a counter.

![Code for the click counter app](https://vue-screencasts-uploads.s3-us-west-2.amazonaws.com/blog-images/use-state-click-counter-2.png)

> For those on smaller screens, don't worry - we'll show a larger, copy-pastable version of the code right before we start talking about the mechanics of each component.

Our plan is to spend quite a bit of time dissecting this example - including some design choices that aren't directly related to `useState` - then tackle a more complicated example with `useState` to see how both frameworks' solutions bend and break with increased requirements.

Some quick notes before we go farther:

- I'm relatively new to React, so when possible I'm going to rip code straight from documentation and well-regarded tutorials.  If there's a better way of doing things, tell me in the comments.
- We're "flipping" the script and template tags for Vue's Single File Components, so the code is easier to compare to React.  Normally the template tag would go first, then the script tag (and then the style tag, which we've left off of the screenshots)

Okay, those caveats done, let's compare these two pieces of code.

### Aesthetics and Readability

Here's the code comparison for the Click Counter again.

![Code for the click counter app](https://vue-screencasts-uploads.s3-us-west-2.amazonaws.com/blog-images/use-state-click-counter-2.png)

The first thing you'll notice is that there are more lines of Vue code, while React has longer individual lines.  Personally, I find the React code on this example a bit more aesthetically pleasing because I have a personal vendetta against extra lines and boilerplate code, but the Vue code is a bit easier to digest.

This is especially true with how they've differentiated the pure Javascript portion of the code from template portion of the code.  Vue has a `script` tag and a `template` tag which clearly differentiate them.  In React, they save a few lines by putting it all in one function and asking you to remember that the setup goes in the main body of the function and the template goes in the return value (except when it doesn't).

I think in general, Vue's boilerplate can look bad *for very small examples* like the following:

```jsx
// React
const [count, setCount] = useState(0);
```

```jsx
// Vue
setup () {
  return {
    count: ref(0)
  }
}
```

However, if you add a couple more pieces of state, we can see that Vue's code starts looking like the cleaner option.

```jsx
// React
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(5);
const [count3, setCount3] = useState(27);
```

```jsx
// Vue
setup () {
  return {
    count: ref(0),
    count2: ref(5),
    count3: ref(27)
  }
}
```

And a huge chunk of the boilerplate can be completed by starting your file with the `vbase-3` auto-complete in VSCode, so with Vue you'll end up typing about the same number of characters or less.

There's also [an RFC in progress](https://github.com/vuejs/rfcs/blob/sfc-improvements/active-rfcs/0000-sfc-script-setup.md) for reducing that boilerplate significantly.

Now let's look at `useState` and `ref` directly.  

## useState vs ref

They're not exactly equivalent, but `ref` (short for "reactive reference") can easily be used to accomplish the same functionality as `useState`.

Let's first look at how `useState` is used in React.

## useState

Here's the code for the React component.

```js
import { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
```

`useState` is a function that takes one argument, which is the initial state.  It returns an array with two values - the initial state, and then a function which can be used to change the state.

You can name the two items in the array anything you want, and you can handle the return array any way you want, but I personally don't see why you'd do anything except the following one-liner that uses array destructuring.

```jsx
const [foo, setFoo] = useState(initValue)
```

But if you want to go nuts and name your stuff in a different (worse) manner, React's not going to stop you:

```jsx
// please don't do this
const myUseStateExample = useState(30)
const foo = myUseStateExample[0]
const aGoodWayToChangeFoo = myUseStateExample[1]
```

So, presuming we're using the standard naming, we can then use `count` for the count, and `setCount(newValue)` to change the value of `count`.

```jsx
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
  Click me
</button>
```

The question I had, as a relative React outsider, is this: why have a variable that can't be changed, and then a function whose sole purpose is to change that variable?  Why not just let `count = newValue` be what sets the count?

The reason, without getting too into the weeds, is that React's component lifecycle and State APIs - which were created before React Hooks and are what the Hooks are "hooking" into - require it.  If you just use `count = newValue`, `count` won't update properly everywhere it's used because React doesn't know that anything's changed.  In general, it appears that you won't have to think about the component lifecycle or the old APIs much while using the Hooks API... unless you're curious enough to dig into why some hooks work the way they do.

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Yeah, that's close. Hooks let you "hook into" the React component lifecycle and state APIs without creating a class or using the methods, but all the ideas that make React React are still the same.</p>— Go JS! (@JS_Cheerleader) <a href="[https://twitter.com/JS_Cheerleader/status/1293424865165230080?ref_src=twsrc^tfw](https://twitter.com/JS_Cheerleader/status/1293424865165230080?ref_src=twsrc%5Etfw)">August 12, 2020</a></blockquote> <script async src="[https://platform.twitter.com/widgets.js](https://platform.twitter.com/widgets.js)" charset="utf-8"></script>

## ref

Here's the code for the Vue component:

```vue
<script>
import { ref } from 'vue'

export default {
  setup () {
    return {
      count: ref(0)
    }
  },
}
</script>

<template>
  <p>You clicked {{count}} times</p>
  <button @click="count++">
    Click me
  </button>
</template>
```

In Vue, we use `ref` to create a reactive reference to a value.

```jsx
setup () {
  return {
    count: ref(0)
  }
}
```

Now, in our template, we can display and set `count`, and it will act like a normal Javascript variable.

```html
<p>You clicked {{count}} times</p>
<button @click="count++">
  Click me
</button>
```

Note the simplicity of this. We can just use `count++`, or `count += 1`, or any number of other easy Javascript shortcuts, rather than declaring a new function and feeding a new value into `setCount` (`() ⇒ setCount(count + 1)` vs `count++`). Behind the scenes, Vue turns that one line of Javascript (`count++`) into an executable function that's run when the click event is fired. Behind the scenes, Vue updates every instance of `count` when we run `count++`.

One reason it can do this is because it's using the custom event listener directive `@click`.  The custom syntax allows for simpler code than if we had to use `onClick`.

I really like the simple code created by Vue's approach. By hiding the layers of complexity, we can get something that's easy to write and easy to read.

But you may be curious about that complexity. How does Vue know to change every instance of `count` when we run `count++`? If React has to use `setCount`, why doesn't Vue have to do something similar?

The short answer is that Vue *does* do something similar, but they hide it from you so you don't have to think about it - that's one more issue off your plate.

Of course, that complexity-hiding isn't always perfectly seamless.  For example, let's look at how we set `count` within our script block.  Now we have to use `count.value`.

```jsx
setup () {
  let count = ref(0)
  const addValue = (numAdded) => {
    count.value = count.value + numAdded
  }
  return {
    count,
    addValue
  }
}
```

The reason is that Vue's reactivity system requires an object in order to function properly, so `ref` creates an object like `{value: 0}`.  In the template, Vue hides this complexity from you and lets you access and change `count` without specifically referencing the `value` property, but in the script block you no longer have this shortcut.

## Comparing the code so far

So far, while I personally prefer the Vue code, objectively they're neck and neck.

Both are fairly straightforward once you start playing around with them, with a few slight paper cuts on either side.

React has fewer lines in the setup code, but there's the inconvenient separation between `foo` and `setFoo`, and the whole naming foot-gun, that makes the API less easy to use than it could be.

Vue has  some great conveniences (compare `@click="count++"` to `onClick={() => setCount(count + 1)}`), but slightly more starter boilerplate, and you have to remember to use `count.value` instead of `count` while in the script block.

The big difference I see, philosophically, is that

- **React wants their APIs to be "pure functional code" and close to basic Javascript,** even if it means foregoing solutions that are more convenient for the developer
- **Vue wants their APIs to be easy to write and read,** even if it means custom syntax and slightly more starter boilerplate

Let's see how those philosophical differences inform a more complex case.

# Reactive Objects

In this example, we're going to be using a short form that has two inputs - first name and last name.

This particular example was taken from [Codevolution's wonderful Hooks series on YouTube](https://www.youtube.com/watch?v=-3lL8oyev9w&list=PLC3y8-rFHvwisvxhZ135pogtX7_Oe3Q3A&index=4), and I then created a Vue version that did the same thing.

![Code comparison for form component](https://vue-screencasts-uploads.s3-us-west-2.amazonaws.com/blog-images/use-state-form-code.png)

First, let's explore the React version

## useState and objects


```jsx
import { useState } from 'react'

function Example() {
  const [name, setName] = useState({first: '', last: ''});

  return (
    <form>
      <input
        type="text"
        value={name.first}
        onChange={e => setName({...name, first: e.target.value})}
      />
      <input
        type="text"
        value={name.last}
        onChange={e => setName({...name, last: e.target.value})}
      />
      <h2>Your first name is - {name.first}</h2>
      <h2>Your last name is - {name.last}</h2>
    </form>
  )
}
```

Our first line is pretty much the same as last time - we get `name` and `setName` from `useState`, and feed in a default value.

Then when we're displaying the first name and last name, it's also pretty much the same - we use `{name.first}` and `{name.last}`.

Where it gets tricky is in the input.

```jsx
<input
  type="text"
  value={name.first}
  onChange={e => setName({...name, first: e.target.value})}
/>
```

The input has a `value`, which is `{name.first}`.  That's straightforward enough.

Then we have `onChange`.  It uses the native `onChange` event listener, which takes a function.  That function has one argument, an event.  You can use `.target.value` on that event to get the input's new value.  Then you splat `...name` in front of that to turn it into the object that `setName` wants.

These lines are... fine, I guess. Once you dig into them you can see what everything's doing, and it's using native Javascript and html syntax, so you don't have to learn any new syntax if you already have a firm grasp of those technologies.  But there's sure a lot of *noise* and it's easy to mix things up.
Here's an easy error to make:

```jsx
<input
  type="text"
  value={name.first}
  onChange={e => setName({first: e.target.value})}
/>
```

Can you guess what happens with this code?

Well, `setName` completely replaces the `name` with whatever argument it's given, so that means that the code above will erase the value of `name.last` and any other key on the `name` object.  You must remember to splat the old object (`setName({...name, key: newValue})`) every time.

Here's another easy error to make:

```jsx
<input
  type="text"
  value={name.first}
/>
```

Or

```jsx
<input
  type="text"
  value={name.first}
  onChange={newVal => setName({...name, first: newVal})}
/>
```

So with all these easy ways to forget something and screw things up, why is it constructed this way?

First, let's look at the Vue solution.

## ref and objects

Here's what the Vue code looks like:

```jsx
<script>
import {ref} from 'vue'

export default {
  setup(){
    return { 
      name: ref({first: '', last: ''})
    }
  }
}
</script>

<template>
  <form>
    <input
      type="text"
      v-model="name.first"
    />
    <input
      type="text"
      v-model="name.last"
    />
    <h2>Your first name is - {{name.first}}</h2>
    <h2>Your last name is - {{name.last}}</h2>
  </form>
</template>
```

The `ref` works the same as last time (but remember: if you use it in the script, you have to do `name.value.first`).

Using it in the template also works the same as last time.

And the input tag is... very simple.

```jsx
<input
  type="text"
  v-model="name.last"
/>
```

Honestly, it could just be

```jsx
<input type="text" v-model="name.last" />
```

So, so simple.

All you have to know about `v-model` is that it acts like a "two-way binding".  That means that, whenever the input changes, `name.last` changes.  And whenever `name.last` changes elsewhere, what's shown in the input changes.

But notice I said it "acts like" a two-way binding.

That's because this is just a shortcut for the following code.

```jsx
<input
  type="text"
  :value="name.first"
  @input="name.first = $event.target.value"
/>
```

You'll notice some similarities here to the React code.  We don't have to do the object splat, and this code is simpler in other ways as well, but we're still having a one-way-bound value and an event (`@input`) that changes the value based on `event.target.value`.

## Comparing React and Vue on reactive object

This one is a massive win for Vue.

I mean, look at the difference.

```jsx
// React
<input
  type="text"
  value={name.first}
  onChange={e => setName({...name, first: e.target.value})}
/>
```

```jsx
// Vue
<input type="text" v-model="name.first" />
```

The Vue code is clear as day, while the React code has a bunch of moving parts that, let's be honest, will almost always be set up the exact same way unless the coder makes a mistake.

So why is React like that?

Three reasons.

First, Vue is fine introducing new syntax like `v-model` or the `@input` event listener directive.  React, on the other hand, wants to use native Javascript and Html as much as possible.

The second is functional purity. `setName` replaces the entire object, so we have to splat the old `name` object or we'll end up erasing data.  React has made the decision that avoiding mutation is worth the cost of you remembering to do extra steps.

Third, they want to avoid two-way data binding.

Two-way data binding, in addition to being Not Functional, has some surprising effects if you misuse it and have multiple layers of two-way data bindings.  It gets harder to track down errors.  We learned this the hard way in the early days of Javascript frameworks.

So instead of using two-way data binding, React devs now have to specify one-way data binding and an event. Together they're basically two-way data binding with extra steps, but they're not *technically* two-way data binding.

Vue says "why have all the extra steps?" and gives you a nice tool to solve the issue.  Of course, there *are* still bad things that can happen with too much two-way data binding, so Vue has some guardrails that prevent you from accidentally making all the old mistakes again.  Generally, you'll use `v-model` with low-level form inputs and a few other places where it's convenient, and have custom event code for other components.

# Conclusion

Originally I had several more examples, but they ended up going deep into other parts of React and Vue without shedding much more light on the `useState` hook... so I'm saving those for my full React vs Vue comparison guide.  Now it's time to review what we've learned.

Through exploring some uses of `setState`, and how to replicate those uses with Vue's Composition API, we've seen pretty clearly a difference of philosophy between React and Vue.

React values pure functional programming and APIs that are clearly recognizable as Javascript functions (except JSX, and that's still pretty clearly a Javascript function once you know how it works).  This can be quite intellectually satisfying, especially for someone like me who first learned how to code with a LISP dialect.

Vue values developer ergonomics and making code clear and easy to read, once you learn a couple new pieces of syntax.  This can result in fewer bugs and fewer wasted dev-hours.

The question you need to ask yourself when choosing one of these frameworks is - which of those sets of values are more important to you?

### What Now?

To get the full React vs Vue guide for free when it's released, [sign up for my mailing list](https://www.vuetraining.net/react-vs-vue-guide-signup).  

You can also [follow me on twitter](https://twitter.com/VueScreencasts).  While you're there, follow [Lachlan Miller](https://twitter.com/Lachlan19900) and [JS Cheerleader](https://twitter.com/JS_Cheerleader), both of whom shared valuable knowledge that improved this post.

If you're interested in learning Vue, I've created [a training course with hands-on learning and guaranteed results](https://www.vuetraining.net/).

If you're already using Vue successfully and want assistance, I have consulting availability on a limited basis.  <a href="mailto:jeffrey@vuetraining.net" target="_blank">Contact me for details</a>.