Vue 3 for React developers: Side-by-side comparison with demos
Tags: vue, react, webdev
Published: 2023-07-26 17:45:35

**Written by [Sebastian Weber](https://blog.logrocket.com/author/sebastianweber/)✏️**

The goal of this article is to offer React developers a quick introduction to Vue development. It's structured as a guide, recreating common React use cases and demonstrating how they are implemented with Vue. Not every aspect is explained in detail, but corresponding React examples are provided for [every Vue code snippet in this GitHub repo](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs).

* * *

### _Table of contents_

*   [What we’ll cover](#what-we-cover)
    *   [About our companion React and Vue projects](#about-companion-react-vue-projects)
    *   [Tooling support](#tooling-support)
*   [Single-file components in Vue vs. JSX in React](#single-file-components-vue-vs-jsx-react)
*   [Using the Composition API](#using-composition-api)
    *   [A quick note on styling Vue 3 components](#quick-note-styling-vue-3-components)
*   [Conditional rendering in templates with Vue directives](#conditional-rendering-templates-vue-directives)
    *   [Rendering lists of items with `v-for`](#rendering-lists-items-v-for)
*   [Rendering JavaScript via text interpolation](#rendering-javascript-text-interpolation)
*   [Multi-root components with Vue fragments and fallthrough attributes](#multi-root-components-vue-fragments-fallthrough-attributes)
*   [Accessing DOM nodes with template refs](#accessing-dom-nodes-template-refs)
*   [Working with props in Vue 3](#working-props-vue-3)
    *   [Prop type validation with runtime declarations](#prop-type-validation-runtime-declarations)
    *   [Prop types with type-based annotations: Pure TypeScript approach](#prop-types-type-based-annotations-pure-typescript-approach)
    *   [Destructuring props is an anti-pattern](#destructuring-props-anti-pattern)
*   [Vue slots: Rendering children or props](#vue-slots-rendering-children-props)
    *   [Simulating React’s render props with scoped slots](#simulating-react-render-props-scoped-slots)
*   [Working with Vue’s Reactivity API and two-way data binding](#working-vue-reactivity-api-two-way-data-binding)
    *   [Working with reactive state objects using `reactive`](#working-reactive-state-objects-using-reactive)
    *   [Working with reactive variables using `ref`](#working-reactive-variables-using-ref)
    *   [Controlling form input fields](#controlling-form-input-fields)
    *   [Improving two-way data binding with `v-model`](#improving-two-way-data-binding-v-model)
    *   [Form submission](#form-submission)
    *   [Listening to custom events in parent components](#listening-custom-events-parent-components)
*   [Using Vue’s composables as React Hook counterparts](#using-vue-composables-react-hook-counterparts)
    *   [Composables with flexible inputs and dynamic outputs](#composables-flexible-inputs-dynamic-outputs)
    *   [Similarities and differences between React Hooks and Vue composables](#similarities-differences-between-react-hooks-vue-composables)
*   [Rendering and component lifecycles with Vue composables](#rendering-component-lifecycles-vue-composables)
    *   [Hooking into lifecycle stages and reacting to state changes](#hooking-lifecycle-stages-reacting-state-changes)
    *   [Watching state changes and performing side effects](#watching-state-changes-performing-side-effects)
    *   [Component rendering](#component-rendering)

* * *

## What we’ll cover

This article is for React devs who are familiar with creating projects purely with functional components and Hooks, but who are interested in working with Vue 3\. Basic TypeScript know-how is helpful but not required. 

I have over five years of experience with React and many thoughts went into what would most assist an experienced React dev wanting to learn Vue quickly. So, in this post, we’ll only work with client-side Vue and the [Composition API](https://vuejs.org/guide/introduction.html#composition-api) with TypeScript and compiler macros. 

We’ll also be using [single-file component (SFC)](https://vuejs.org/guide/scaling-up/sfc.html) `.vue` files and the following `script` "flavor": `<script setup lang="ts">`. In React, these would be similar to `.jsx`/`.tsx` files, but are different because our template, logic, and styles are all contained in one place. 

Here’s what we won’t cover in this piece:

*   "Legacy" Vue variants (e.g., using the Options API)
*   "Exotic" methods of creating Vue components (e.g., [using JSX with Vue](https://blog.logrocket.com/using-jsx-with-vue/))
*   No global state management (i.e., no Pin[i](https://pinia.vuejs.org/)a, Redux, etc.)
*   No routing or other advanced concepts — just the core concepts of Vue
*   No styling of Vue components besides the foundations

### About our companion React and Vue projects

In preparation for this article, I built identical to-do apps, one each with React and Vue 3\. You can [find both apps in this GitHub repository](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs) in their dedicated folders:

```plaintext
companion-project-vue3-for-react-devs/
├─ react/
├─ vue/
```

For the sake of brevity and clarity, we’ve only directly included Vue code snippets in this article, but links to the corresponding React snippets in the [GitHub project](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/tree/main/react) are linked below each Vue snippet.

### Tooling support <a name="tooling-support">

Like with JSX in React components, you’ll need tooling support from your editor to work with `.vue` files. IntelliJ and WebStorm both support `.vue` files out of the box, but the recommended way to use Vue with the Composition API and TypeScript in VS Code is to use the extension [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) with its [TypeScript support](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 

It's also helpful to use ESLint in Vue projects. I believe that it also helps you learn a new framework. The official ESLint plugin for Vue is [`eslint-plugin-vue`](https://eslint.vuejs.org/), which works with `.vue` files. 

If you want, you can read about [setting up a ESLint and Prettier workflow](https://doppelmutzi.github.io/2022-10-02-vue-eslint-prettier-ts-vscode/) with automatic code reformatting on save when using `eslint-plugin-vue` to enforce recommended Vue rules.

## Single-file components in Vue vs. JSX in React

An SFC typically consists of three sections: `script`, `template`, and `style`. If you don't need component styling, you can omit the `style` tag. The order of these sections is up to you. I prefer to put `template` before `script`, but the official Vue documentation recommends using `script` first. 

The `template` section is where you put your markup; it is the Vue counterpart to JSX files in React's component architecture. While Vue does support using [JSX with render functions](https://vuejs.org/guide/extras/render-function.html#jsx-tsx), this variant is not commonly used or recommended by the community. 

The following code snippet of the `Headline` component shows the typical structure of a SFC:

```javascript
<!-- Headline.vue -->
<template>
  <!-- the JSX "pendant" of React -->
  <h1 :style="{ color }">{{ text }}</h1>
</template>
<script setup lang="ts">
  /* 
  Here comes the component logic. We focus on this section later in the composition API section 
  */

  /* ... */
  const color = computed(() => {
    // ...
  });
</script>
<style scoped lang="scss">
  /* Import or define styles here 
  based on the defined lang attribute */ 

  h1 {
    font-size: 100px;
    font-weight: 100;
    text-align: center;
  }
</style>
```

Find the [React equivalent here](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/Headline.jsx).

## Using the Composition API

Look at the code snippet above and notice the keyword `setup` in the `script` tag: this is the recommended way to use the [Composition API](https://vuejs.org/guide/extras/composition-api-faq.html#what-is-composition-api), an umbrella term that combines the [Reactivity API](https://vuejs.org/api/reactivity-core.html) and [lifecycle hooks](https://vuejs.org/api/composition-api-lifecycle.html). 

The Composition API can be compared to the [transition from class-based React components to React Hooks](https://blog.logrocket.com/refactor-react-components-hooks/) because it brings similar advantages, such as better code organization by feature and improved separation of concerns. With it, we can use variables defined inside the `script` tag directly in the `template` section, and use functions like `defineProps` without importing them. 

The latter is possible because `<setup script>` uses [compile-time macros](https://vuejs.org/guide/introduction.html#api-styles) so that explicit imports are no longer necessary. 

In the following example, we use `defineProps` without importing it and the props are automatically available inside of `template`:

```javascript
<!-- DeleteButton.vue -->
<template>
  <button @click="onClick()">{{ label }}</button>
</template>
<script setup lang="ts">
defineProps({
  label: {
    type: String,
    default: "❌",
  },
  onClick: {
    type: Function,
    default: () => {
      // no op
    },
  },
});
</script>
```

Find the [React example here](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/DeleteButton.jsx).

### A quick note on styling Vue 3 components

As we discussed at the top, this article will not discuss styling in detail. But just like with React, you can [style components](https://vuejs.org/api/sfc-css-features.html#sfc-css-features) using either vanilla CSS `class` and `style` tags or a CSS-in-JS approach, such as [CSS Modules](https://vuejs.org/api/sfc-css-features.html#css-modules). 

In the previous section, we used [scoped styles](https://vuejs.org/api/sfc-css-features.html#scoped-css) (to avoid side-effects) with the CSS preprocessor Sass to style the `h1` tag. We also [bound inline styles with `:style`](https://vuejs.org/guide/essentials/class-and-style.html#binding-inline-styles), which refers to the `color` variable defined in the `script` section. 

See how we [bind classes](https://vuejs.org/guide/essentials/class-and-style.html#binding-html-classes) in Vue below:

```javascript
<!-- ToggleButton.vue -->
<template>
  <div
    class="toggle-button"
    :class="{ 'all-checked': allChecked, 'is-visible': isVisible }"
    @click="onToggle"
  >
    <button>&or;</button>
  </div>
</template>
<script setup lang="ts"> /* ... */ </script>
<style scoped lang="scss">
  .toggle-button {
    /* ... */
    &.is-visible > button {
      display: block;
    }
    &.all-checked > button {
      color: #e6e6e6;
    }
  }
</style>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/ToggleButton.jsx). 

In the `template`, we:

*   Assign the attribute `class` to our class `toggle-button`, as you would do with regular HTML
*   Assign the class `all-checked` if the variable `allChecked` is truthy
*   Assign the class `is-visible` if the variable `isVisible` is truthy

However, if you calculate the class name dynamically, you should opt for `:class`. `:class` is the shorthand form of the [`v-bind` directive](https://vuejs.org/api/built-in-directives.html#v-bind) (`v-bind:class`); we’ll discuss directives more comprehensively in the next section.

## Conditional rendering in templates with Vue directives

Vue provides many built-in directives, but the most common for [conditional rendering](https://vuejs.org/guide/essentials/conditional.html) are:

*   `v-if`
*   `v-else`
*   `v-else-if`
*   `v-show`

The difference between `v-if` and [`v-show`](https://vuejs.org/guide/essentials/conditional.html#v-show) is that the latter will always stay in the DOM and is just visually hidden, pretty much like `display:none` vs. `visibility:hidden` in CSS. 

The UI part of a Vue component goes into the `template` section with the help of an [HTML-based template syntax](https://vuejs.org/guide/essentials/template-syntax.html#template-syntax). Similar to React, you have to import a child component in an SFC and use it with a custom tag that can optionally receive props. 

In the next example, `label`, `active`, and `on-click` are assigned as props for `FilterButton`:

```javascript
<!-- FilterButtons.vue -->
<template>
  <div class="filter-buttons">
    <FilterButton
      label="All"
      :active="filterIndex === FilterIndex.ALL"
      :on-click="handleAllClick"
    />
   <!-- ... -->
  </div>
</template>
<script setup lang="ts">
  // import child component to use it in template section
  import FilterButton from "./FilterButton.vue";
  // ...
</script>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/FilterButtons.jsx). 

Let me briefly clarify what you see in the snippet above. `label` is a prop that receives a string value of `All`. The assigned string values for the other props are interpreted as JavaScript expressions (remember, `:` is short for `v-bind`). 

In contrast to conditional rendering in React with vanilla JavaScript concepts, like the [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator) in Vue's `template` section, you make use of [built-in directives](https://vuejs.org/api/built-in-directives.html#built-in-directives). 

The following snippet shows how to use `v-if` to render the component `ClearButton` conditionally whenever the variable `showClearButton` is truthy:

```javascript
<template>
  <!-- ActionBar.vue -->
  <div class="action-bar" :style="theme">
    <!-- ... -->
    <ClearButton v-if="showClearButton" :on-click="handleClearClick" />
  </div>
  <div class="drop-shadow" />
</template>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/ActionBar.jsx). 

You can also use `v-if` for an entire `template` tag instead of a component, like so:

```html
<template v-if="showIt">
```

Consequently, you can nest `template` tags in the root `template` tag to organize your UI code. We will see examples in the [section about named slots](#vue-slots-rendering-children-props).

### Rendering lists of items with `v-for`

A common use case for conditional rendering is rendering a list of components: you add the [`v-for` directive](https://vuejs.org/api/built-in-directives.html#v-for) to the component `TodoItem` when you want to render a number of instances, based on a variable storing iterable data. 

Take a look:

```javascript
<template>
  <!-- TodoList.vue -->
  <div class="todo-list">
    <TodoItem v-for="todo in filteredTodos" :key="todo.id" :todo="todo" />
  </div>
</template>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoList.jsx). 

In this example, `filteredTodos` is an array and `todo` represents the current iterated item. As in React, you have to [specify a key](https://blog.logrocket.com/unfavorable-react-keys-unpredictable-behavior/) (`:key`) to prevent bugs during re-rendering. 

We then assign the `id` prop of the current item to the `key` prop of `TodoItem`. Finally, we pass the whole iterated item as a prop to the underlying child component with `:todo="todo"`.

## Rendering JavaScript via text interpolation

If you want to render a variable or JavaScript expression via [text interpolation](https://vuejs.org/guide/essentials/template-syntax.html#text-interpolation) in a Vue `template`, you use double curly braces ("mustache syntax"). 

The mustache syntax [does not work inside of attributes](https://vuejs.org/guide/essentials/template-syntax.html#attribute-bindings). This is where the [`v-bind` directive](https://vuejs.org/api/built-in-directives.html#v-bind) comes into play. In the below example, we use such a directive with the `style` attribute (`:style`, or the long form `v-bind:style`) as we render the value of the prop `label`:

```javascript
<!-- FilterButton.vue -->
<template>
  <button
    :style="{
      borderColor: active ? theme.color : 'transparent',
      backgroundColor,
      color,
    }"
    @click="onClick()"
  >
    {{ label }}
  </button>
</template>
<script setup lang="ts">
  defineProps({
    label: String,
    active: Boolean,
    onClick: Function,
  });
  /* ... */
</script>
```

This is different from React, where you use only single curly braces (see `{ label }` in [`FilterButton.jsx`](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/FilterButton.jsx), our corresponding React example).

## Multi-root components with Vue fragments and fallthrough attributes

In Vue 3, there is no limitation on single-root elements inside of the `template` tag, thanks to Vue fragments, which is conceptually similar to React's fragment element (`<></>`):

```javascript
<template>
  <!-- App.vue -->
  <MailForm @subscribe-newsletter="onSubscribeNewsletter" />
  <Headline font-color="red" text="todos" class="headline" />
  <Todos />
</template>
```

Compare this to [our React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/App.jsx). 

Vue features a mechanism called [fallthrough attributes](https://vuejs.org/guide/components/attrs.html), which passes attributes to child components even if they are not defined as props (e.g., `class`, `style`, `type`, etc.). They are automatically assigned to the route element of the component. 

Consider the following example, where I use the `Headline` component inside of the `App` component:

```javascript
<!-- App.vue -->
<template>
  <!-- ... -->
  <Headline font-color="red" text="todos" class="headline" />
</template>
<!-- Headline.vue -->
<script lang="ts" setup>
/* ... */
const prop = defineProps({
  fontColor: {
    type: String as PropType<HeadlineColor>,
    default: "red",
  },
  text: {
    type: String,
    required: true,
  },
});
```

Even though the defined props in `Headline.vue` are `fontColor` and `text`, the `class` attribute is automatically available. If `Headline` were a component with a single-root element, the `class` attribute would be automatically applied to it. 

However, I artificially created a multi-root component to demonstrate how to handle fallthrough attributes in this situation. Now, Vue needs assistance with where to assign these attributes:

```javascript
<template>
  <!-- Headline.vue -->
  <div><!-- useless; just for demo purposes --></div>
  <h1 :class="$attrs.class" :style="{ color }">{{ text }}</h1>
</template>
```

Here’s [our React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/Headline.jsx). 

The `class` attribute, along with all other fallthrough attributes, is stored in the `$attrs` variable (and defined in `App.vue`), which is automatically available in the `template` section.

## Accessing DOM nodes with template refs

There are situations where you have to access DOM elements directly, e.g., to focus an input field after mounting a corresponding Vue component. It's pretty similar to React, where you use `useRef` for this. 

With the help of [template refs](https://vuejs.org/guide/essentials/template-refs.html), we focus the input field to enter to-dos just after the `TodoInput.vue` component mounts:

```javascript
<!-- TodoInput.vue -->
<template>
  <div class="todo-input">
    <!-- ... -->
    <input
      ref="inputRef"
      v-model="inputValue"
      :placeholder="placeholder"
      @keyup.enter="onEnter"
    />
  </div>
</template>
<script setup lang="ts">
/* ... */
import { ref, onMounted, type Ref } from "vue";
/* ... */
const inputRef: Ref<HTMLInputElement | null> = ref(null);
onMounted(() => {
  if (inputRef.value != null) {
    inputRef.value.focus();
  }
});
/* ... */
```

This is [what this looks like in React](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoInput.jsx). 

We create a `ref` and assign it to a variable (`inputRef`). In the `template` section, we assign this variable to the template ref (`ref="inputRef"`). Now, we can access the input field via `inputRef` and call the `focus()` method. 

In order to avoid TypeScript errors in this example, we need to type the `inputRef` with the help of `Ref` and communicate that the variable can be either `null` in the beginning or of type `HTMInputElement`. In addition, we have to check in the `onMounted` callback first, to see whether or not `inputRef.value` has been assigned to the DOM element. 

`onMounted` is an example of a Vue lifecycle method, which we will discuss later.

## Working with props in Vue 3

The concept behind Vue's prop mechanism is similar to React’s. Let's look at an example (`FilterButtons.vue`) that passes props to a child component (`FilterButton.vue`).

```javascript
<!-- FilterButtons.vue -->
<template>
  <FilterButton
    label="All"
    :active="filterIndex === FilterIndex.ALL"
    :on-click="handleAllClick"
  />
</template>
```

See the [React example in full](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/FilterButtons.jsx). 

Remember, a colon (`:`) before a prop name indicates that the value is not a string, but should be interpreted as the abbreviation for the built-in directive `v-bind`:

```javascript
v-bind:active="filterIndex === FilterIndex.ALL"
```

In contrast, the `label` prop only receives a hard-coded string value (`ALL`), so the following variant works, but is unnecessary:

```javascript
:label="'All'"
```

Let's look at the part where we define props. With `defineProps`, you define the props along with its data types:

```javascript
<script setup lang="ts">
  // FilterButton.vue
  defineProps({
    label: String,
    active: Boolean,
    onClick: Function,
  });
</script>
```

Here’s the [React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/FilterButton.jsx). 

Now, there are some interesting things to consider. First, `defineProps` can be used without importing it because it’s automatically available inside `<setup script>`, thanks to Vue's Composition API and compiler macros. 

Second, with this single call, you can directly use these props within the `template` section:

```javascript
<template>
  <button
    :style="{
      borderColor: active ? theme.color : 'transparent',
      backgroundColor,
      color,
    }"
    @click="onClick()"
  >
    {{ label }}
  </button>
</template>
<script setup lang="ts">
  defineProps({
    label: String,
    active: Boolean,
    onClick: Function,
  });
</script>
```

However, if you need to access props within the `script` tag, you have to assign them to a variable:

```javascript
<script setup lang="ts">
// TodoItem.vue
const props = defineProps({
  todo: Object
});
const isCrossedOut = computed(() => props.todo.checked === true);
// ...
</script>
```

[Take a look at how this is done in React](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoItem.jsx).

### Prop type validation with runtime declarations

You can pass an object to the `defineProps` macro to declare the props that a component can receive. The data types in the above example (`String`, `Boolean`, and `Function`) are conceptually similar to [runtime type checking for React props](https://blog.logrocket.com/validate-react-props-proptypes/). 

In contrast to React, you don't have to import an additional library for this in Vue; Vue uses JavaScript’s built-in types, all of which are currently supported for [runtime validation](https://vuejs.org/guide/components/props.html#runtime-type-checks):

*   String
*   Number
*   Boolean
*   Array
*   Object
*   Date
*   Function
*   Symbol

As you can see in the following screenshot, you also get a prop warning in the browser’s JavaScript console if you don’t pass the correct data type to props when using Vue: ![Prop type warning in the browser console](http://blog.logrocket.com/wp-content/uploads/2023/07/prop-type-warning-console.png) If you used our previously recommended ESLint config, the linter should call for an improved variant to declare props:

```javascript
defineProps({
  label: {
    type: String,
    required: true,
  },
  active: {
    type: Boolean,
    default: false,
  },
  onClick: { type: Function, required: true },
});
```

Like in React, you can define optional props, for which you have to define default values. You can also mark props as required with `required:true`. 

You may ask, how do we work with non-primitive types like `Object` or `Array`? With the [PropType API](https://blog.logrocket.com/validate-react-props-proptypes/):

```typescript
<script setup lang="ts">
  // TodoItem.vue
  import { type PropType } from "vue";
  defineProps({
    todo: {
      type: Object as PropType<{
        id: number;
        label: string;
        date: string;
        checked: boolean;
      }>,
      required: true,
    },
  });
</script>
```

Here is a [similar React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoItem.jsx). 

The example shows that the `todo` prop has a JavaScript type of `Object`. However, with PropType's generic argument, you can define the properties the object has. In the part between `<>`, we provide a complex type defined with TypeScript. 

Let's look at another way to use TypeScript for prop types in the next section.

### Prop types with type-based annotations: Pure TypeScript approach

Besides runtime declarations, where you pass an argument to `defineProps` at runtime, there is a more elegant variant with [pure TypeScript annotations](https://vuejs.org/guide/typescript/composition-api.html#typing-component-props):

```typescript
<script setup lang="ts">
  // TodoItem.vue
  defineProps<{
    todo: {
      id: number;
      label: string;
      date: string;
      checked?: boolean;
  }>();
</script>
```

Here’s [the above, but in React](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoItem.jsx). 

You can also use custom types or interfaces:

```typescript
<script setup lang="ts">
  interface Props {
    todo: {
      id: number;
      label: string;
      date: string;
      checked?: boolean;
    };
  }
  defineProps<Props>();
</script>
```

Until [version 3.3](https://blog.vuejs.org/posts/vue-3-3#imported-and-complex-types-support-in-macros), Vue did not support importing types from another file — you had to stick to the `PropType` utility. This is no longer a problem, though, and the following is now possible:

```typescript
<script setup lang="ts">
// TodoItem.vue
import type { TodoItemProps } from "./types";
// ...
// this is not possible with vue < 3.3
const props = defineProps<TodoItemProps>();
</script>
```

Another caveat when relying on pure, type-based prop declarations is losing the ability to define prop default values. Instead, you have to use the compiler macro [`withDefaults`](https://vuejs.org/guide/typescript/composition-api.html#props-default-values):

```typescript
<script setup lang="ts">
// DeleteButton.vue

// alternative
/* defineProps({
  label: {
    type: String,
    default: "❌",
  },
  onClick: {
    type: Function,
    default: () => {
      // no op
    },
  },
}); */
type Props = {
  label?: string;
  onClick?: () => void;
};
withDefaults(defineProps<Props>(), {
  label: "❌",
  onClick: () => {
    // no op
  },
});
// ...
</script>
```

Here’s the [React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/DeleteButton.jsx). 

Wondering when to use each variant? Or maybe you’re wondering why we don’t use both variants to have compile-time prop validation based on TypeScript, _and_ runtime prop validation with the runtime declaration. 

The thing is, you have to choose a single variant. Using both at the same time will result in a compiler error: ![Both variants result in errors](http://blog.logrocket.com/wp-content/uploads/2023/07/both-variants-results-error.png) 

If you decide to use type-based annotations, the compiler infers the runtime options automatically. 

Since runtime prop validation is also available with type-based annotations, this variant is recommended by large parts of the Vue community.

### Destructuring props is an anti-pattern

In React, you've most likely destructured props on every component. But with Vue and the Composition API, you [shouldn't do it](https://vuejs.org/guide/extras/reactivity-transform.html#reactive-props-destructure). 

Vue props provided by `defineProps` are reactive objects and you will [lose reactivity](https://dmitripavlutin.com/props-destructure-vue-composition/) when you perform object destructuring. The rule of thumb is: **Don't destructure props**. 

Despite this warning, it looks like this could soon be a thing of the past: with the release of Vue 3.3, destructuring reactive props is now an [experimental feature](https://blog.vuejs.org/posts/vue-3-3#reactive-props-destructure). 

In the `<script setup>` you have to access the properties on the `props` object directly:

```typescript
<script setup>
  const props = defineProps({
    label: {
      type: String,
      required: true,
    }
  });
  console.log(props.label);
</script>
```

If you cannot live without destructuring but are willing to take an extra step, you can use the Reactivity utility function [`toRefs`](https://vuejs.org/api/reactivity-utilities.html#torefs).

## Vue slots: Rendering children or props

Vue provides a mechanism called [slots](https://vuejs.org/guide/components/slots.html), which allows you to nest React components inside of JSX so that content is passed in a `children` prop. 

Below is the React way, which you can [see here](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/AppLayout.jsx):

```javascript
  // AppLayout.jsx
  function AppLayout({  children }) {
    return (
      <LayoutContainer>
        { /* ... */ }
        <Main>{children}</Main>
      </LayoutContainer>
    );
  }
  // App.jsx
  const App = () => {
    return (
    { /* ... */ }
    <AppLayout>
      <Todos />
    </AppLayout>
  );
};
```

In Vue, you use a [slot outlet](https://vuejs.org/guide/components/slots.html#slot-content-and-outlet) to render the passed template content. The above example can be implemented with Vue's default slot. It is a slot without a name; thus, we assign it the name `default` implicitly (`<slot` `name="default">`):

```javascript
  <!-- App.vue -->
  <template>
    <AppLayout>
      <!-- ... -->
      <template #default>
        <Todos />
      </template>
    </AppLayout>
  </template>
<!-- AppLayout.vue -->
<template>
  <div id="app-layout">
    <!-- ... -->
    <main>
      <!-- or <slot name="default"></slot> -->
      <slot>
        🚀 <!-- fallback content -->
      </slot> 
    </main>
  </div>
</template>
```

In our case, the Vue component `Todos` will render in the spot marked by `<slot></slot>`, i.e., the default slot of the `AppLayout` component. 

In contrast to React’s `children` prop, you have more options with Vue's slot mechanism, such as to provide [fallback content](https://vuejs.org/guide/components/slots.html#fallback-content) whenever the parent component doesn’t provide slot content. In the above, we used the rocket emoji as a fallback. This is not limited to text content and can be a nested markup of HTML or other Vue components. 

Let's see how we can make more use of slots in Vue components. You can only have one default slot — the others need to be named, i.e., `newsletter` and `headline`. We use these two named slots below:

```javascript
<!-- App.vue -->
<template>
  <AppLayout>
    <template #newsletter>
      <MailForm @subscribe-newsletter="onSubscribeNewsletter" />
    </template>
    <template #headline>
      <Headline font-color="red" text="todos.vue" class="headline" />
    </template>
    <template #default>
      <Todos />
    </template>
  </AppLayout>
</template>
<!-- AppLayout.vue -->
<template>
  <div id="app-layout">
    <aside>
      <slot name="newsletter"></slot>
    </aside>
    <header>
      <slot name="headline"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
  </div>
</template>
```

This is not possible with a `children` prop in React. You could do the following, but this is not a common pattern from my point of view:

```javascript
// AppLayout.jsx 
function AppLayout({ headline, newsletter, children }) {
  return (
    <LayoutContainer>
      <Aside>{newsletter}</Aside>
      <Header>{headline}</Header>
      <Main>{children}</Main>
    </LayoutContainer>
  );
}
// App.jsx
const App = () => {
  // ...
  return (
    { /* ... */ }
    <AppLayout
      headline={<Headline />}
      newsletter={
        <MailForm onSubscribeNewsletter={ /* ... */ }/>
      }>
        <Todos />
    </AppLayout>
  );
};
```

Here’s the [above React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/AppLayout.jsx). 

Slots also have downsides, including limitations on restricting the content they render. If you rely on props, you have more possibilities with the help of prop type validation and TypeScript. On the other hand, slots allow for much more flexibility. In the end, what you choose is mostly a matter of taste.

### Simulating React's render props with scoped slots

Vue also provides [scoped slots](https://vuejs.org/guide/components/slots.html#scoped-slots), which can be seen as Vue's equivalent to [React `render` props](https://react.dev/reference/react/Children#calling-a-render-prop-to-customize-rendering). These concepts look different and may differ in detail, but they are functionally similar. 

Let's look at a Vue implementation that does the same job as the render prop used in [`TodoList.jsx`](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoList.jsx) (`renderItem`):

```javascript
<!-- Todos.vue -->
<template>
  <!-- ... -->
  <TodoList>
    <template #todo="{ todo }">
      <TodoItem :key="todo.id" :todo="todo" />
    </template>
  </TodoList>
</template>
<!-- TodoList.vue -->
<template>
  <div class="todo-list">
    <slot name="todo" v-for="todo in filteredTodos" :todo="todo"></slot>
  </div>
</template>
```

Here’s a [link to the full React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/Todos.jsx). 

In the `Todos` component, the named slot can receive props, which can be seen as parameters of the `render` prop. In `TodoList`, we pass props to the `slot` tag as usual (`:todo="todo"`). 

In our example, we directly destructured the `todo` prop and used it in the `template` body to render a `TodoItem` component with the right data. The following is an alternative:

```javascript
<!-- Todos.vue -->
<template>
  <!-- ... -->
  <TodoList>
    <template #todo="slotProps">
      <TodoItem :key="slotProps.todo.id" :todo="slotProps.todo" />
    </template>
  </TodoList>
</template>
```

## Working with Vue's Reactivity API and two-way data binding

Similar to `useState`, if you mutate reactive data, the corresponding Vue component updates its view. This is where the [Reactivity API](https://vuejs.org/api/reactivity-core.html) comes into play. 

It is an umbrella term in Vue that combines various concepts and APIs within the topic of reactivity. There are two major concepts behind creating component state as you can with `useState` in React:

1.  Reactive state objects with `reactive`
2.  Reactive variables with `ref`

You can choose between these approaches based on your use case and taste, as both techniques have advantages and disadvantages. Let's start with `reactive`.

### Working with reactive state objects using `reactive`

If you are a long-time React developer, surely you remember how to use state in class-based components: you assign nested objects to `setState`. The situation with `reactive` is comparable because it can only hold objects and not primitive values. 

Let's look at the `MailForm` component that uses a reactive state object:

```javascript
<!-- MailForm.vue -->
<template>
  <!-- ... -->
    <input :value="user.firstName" @input="handleFirstNameChange" />
  <!-- ... -->
</template>
<script setup lang="ts">
  import { reactive } from "vue";
  type User = {
    firstName: string;
    mail: string;
    frequency: "weekly" | "monthly";
  };
  const user = reactive<User>({ firstName: "", mail: "", frequency: "weekly" });
  const handleFirstNameChange = (evt: Event) => {
    user.firstName = (evt.target as HTMLInputElement).value;
  };
  // ...
</script>
```

Let's look at the reactive part now; we’ll discuss the different aspects of two-way data binding step by step in a moment. You can [take a look at the React example here](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/MailForm.jsx). 

The following statement creates the reactive state object `user`. Under the hood, Vue uses a JavaScript `Proxy` object. I decided to use `reactive` instead of individual `ref`s because these variables are meant to be together and constitute a state as a union:

```typescript
const user = reactive<User>({ firstName: "", mail: "", frequency: "weekly" })
```

We use TypeScript and the custom type `User` as generic parameters of `reactive` to guarantee type safety. We then initialize the reactive object with default values for the three properties. 

With a simple mutation, we can update the state object, as you can see here:

```typescript
  user.firstName = // assign a value
```

When we use the properties of this reactive object in the template, the view updates after the mutation (`<input :value="user.firstName"`). 

If you inspect the `MailForm` component in Vue DevTools, you will find the reactive `user` object. ![Inspecting reactive data in Vue DevTools](http://blog.logrocket.com/wp-content/uploads/2023/07/inspect-reactive-data-vue-devtools.png)

### Working with reactive variables using `ref`

A `ref` is a reactive and mutable object with a single property, `value`, to hold its data, which can be primitive data or object types. Any read operation of `value` is tracked and mutation triggers associated effects. 

Conceptually, `ref` cannot be compared to the `useRef` Hook, but their handling is similar because you also access data via the `current` property. Instead, `ref` has more conceptual similarity to `useState` because you create a state variable with either. 

As with `useState`, a best practice here is to create individual `ref`s instead of nested objects. Let's look at an example:

```typescript
<!-- TodoItem.vue -->
<template>
  <!-- ... -->
  <DeleteButton v-show="hover" :on-click="handleDeleteClick" />
  <!-- ... -->
</template>
<script setup lang="ts">
import { ref } from "vue";
// ...
const hover = ref(false);
const onMouseOver = () => {
  return (hover.value = true);
};
const onMouseOut = () => {
  return (hover.value = false);
};
// ...
</script>
```

Here’s the [React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoItem.jsx). 

We created a reactive variable `hover` with an initial falsy value. It gets mutated in the mouse event handlers by assigning new Boolean values to the `value` property. You get an error at development time when you try to pass something other than a Boolean because the `ref` function infers the type. 

For your convenience, Vue automatically unwraps the value when you access the `ref` in the `template` section (`v-show="hover"`). If an object is assigned as a `ref`'s value, the object is made deeply reactive with `reactive`.

### Controlling form input fields <a name="controlling-form-input-fields">

A common use case for frontend apps is to build a form. Let's apply our acquired knowledge about reactive data to create two-way data binding similar to React’s controlled components. 

If you want to refresh your React knowledge about controlled components with `useState`, I suggest this LogRocket article [comparing `useState` vs. `useRef`](https://blog.logrocket.com/usestate-vs-useref/). 

The first form field shows how to implement two-way binding with Vue. The input value is bound to a reactive object (`user.firstName`) as described in the last section:

```javascript
<!-- MailForm.vue -->
<template>
  <!-- ... -->
  <fieldset>
    <!-- ... -->
    <input :value="user.firstName" @input="handleFirstNameChange" />
  </fieldset>
  <!-- ... -->
</template>
<script setup lang="ts">
  import { reactive } from "vue";
  type User = {
    firstName: string;
    mail: string;
    frequency: "weekly" | "monthly";
  };
  const user = reactive<User>({ firstName: "", mail: "", frequency: "weekly" });
  const handleFirstNameChange = (evt: Event) => {
    user.firstName = (evt.target as HTMLInputElement).value;
  };
  // ...
</script>
```

Here’s the [React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/MailForm.jsx). 

In this snippet, we use value binding and event binding. We combine `v-bind` (`v-bind:value`, or the shorter `:value`) with `v-on` (`v-on:input`, or the shorter `@input`) to react to input changes and write them back to the state (`user.firstName`). We react to input change events and call the callback `handleFirstNameChange`.

### Improving two-way data binding with `v-model`

A more elegant variant is to use [`v-model`](https://vuejs.org/guide/components/v-model.html), which encapsulates both value binding and event binding. We can see how this works with the second input field:

```javascript
<!-- MailForm.vue -->
<template>
  <!-- ... -->
  <fieldset>
    <!-- ... -->
    <input v-model="user.mail" />
  </fieldset>
  <!-- ... -->
</template>
```

Here is [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/MailForm.jsx). 

We’ve done the same job but with less code. We don't need an explicit event handler to reflect the `v-model` value to the state variable (`user.mail`). If you’re interested, you can [read in great detail about this topic](https://blog.logrocket.com/using-v-model-vue-3-build-complex-forms/) in another LogRocket article.

### Form submission <a name="form-submission">

Just as with React, we need to prevent the default HTML form behavior by calling `event.preventDefault()` and using our own callback function instead. Sure, you can call this from inside of your event handler method, but it's more concise to use the [event modifier](https://vuejs.org/guide/essentials/event-handling.html#event-modifiers) `prevent` of the submit event (`@submit.prevent`):

```typescript
<template>
  <!-- MailForm.vue -->
  <form @submit.prevent="handleSubmit">
    <!-- ... -->
  </form>
</template>
<script setup lang="ts">
  // ...
  const emit = defineEmits(["subscribe-newsletter"]);
  const handleSubmit = () => {
    emit("subscribe-newsletter", user.firstName);
  };

  // ...
</script>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/MailForm.jsx). 

In our example, `handleSubmit` calls a function created by the macro [`defineEmits`](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits). In the next section, we’ll look at how to use it for event handling.

### Listening to custom events in parent components

In the previous section, `handleSubmit` emits a custom event called `subscribe-newsletter`. As a second argument, we can pass an event payload to `emit`. We can listen to this event in a parent component:

```typescript
<!-- App.vue -->
<template>
  <MailForm @subscribe-newsletter="onSubscribeNewsletter" />
  <!-- ... -->
</template>
<script setup lang="ts">
  // ...
  const onSubscribeNewsletter = (firstName: string) => {
    console.log(`Subscribe newsletter for ${firstName}`);
  };
</script>
```

Here’s the [same example in React](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/App.jsx). 

In `App.vue`, we receive the event with the event binding `@subscribe-newsletter`. We assign a callback function (`onSubscribeNewsletter`), which receives our `string` value as an argument (`firstName`). 

A common way to solve this with React is to pass a callback function as prop to the child component. In our Vue `App.jsx`, we pass a function prop named `onSubscribeNewsletter` that accepts a parameter `firstName` to the `MailForm` component. We then invoke this callback function inside of the child component in the form submission handler. 

With Vue’s DevTools, you can inspect fired events in the `Timeline` section: ![Vue DevTools allows for inspecting events](http://blog.logrocket.com/wp-content/uploads/2023/07/vue-devtools-allows-inspecting-events.png)

## Using Vue's composables as React Hook counterparts

A [Vue composable](https://vuejs.org/guide/reusability/composables.html) is similar to a React custom Hook. It encapsulates non-UI Vue code into a sole function. Composables leverage the Composition API to reuse stateful Vue logic in components. An ordinary function becomes a composable when it makes use of the aforementioned API methods part of the `vue` package, such as `ref`, `onMounted`, etc. 

Let's look at a simple example composable, `useTitleSimple`, which updates the document title. The naming convention is clearly inspired by React, so your composables also start with `use<SomeSemanticName>`. 

The goal of our example is to adjust the title automatically using a watcher. Whenever we update the composable’s associated reactive variable, the title adjusts accordingly:

```typescript
// composables/useTitle.ts
import { type Ref, watchEffect } from "vue";
/* ... */ 
export const useTitleSimple = (title: Ref<string>) => {
  watchEffect(() => {
    document.title = title.value;
  });
};
```

Here is [what this looks like in React](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/hooks/useTitle.js). 

The composable accepts a `ref` named `title` of type `string`, which becomes `Ref<string>`. We assign the value of this `ref` (`title.value`) to `watchEffect` to update the document's title. 

Whenever the reactive `title` is updated outside of the composable, it triggers the watcher and is re-run. 

The following listing shows how to use the above composable inside the `TodoInput` component. We assign the title of the last created to-do to the document's title:

```typescript
<!-- TodoInput.vue -->
<template>
    <!-- ... -->
    <input
      <!-- omit other props -->
      v-model="inputValue"
      @keyup.enter="onEnter"
    />
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useTitleSimple } from "@/composables/useTitle";
/* ... */
const inputValue = ref("");
// save the last created todo in a reactive variable
// init the reactive variable with the current document title
const recentTodo = ref<string>(document.title);
// use the last todo as document title
useTitleSimple(recentTodo);
/* ... */
const onEnter = (evt: Event) => {
  const { value } = <HTMLInputElement>evt.target;
  if (value.trim() !== "") {
    /*
      create a new todo, 
      not relevant for this example 
    */

    /* mutation of reactive variable triggers 
    the watcher inside of the composable */
    recentTodo.value = inputValue.value;

    /* clear the input field */
    inputValue.value = "";
  }
};
</script>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoInput.jsx). 

Let's break down the important parts of this code snippet. In the `template` section, we use `v-model="inputValue"` to bind a `ref` named `inputValue` to an input field. Additionally, we define a keyboard event handler: `@keyup.enter="onEnter"`. 

Whenever the user types into the input field and presses **Enter**, the callback `onEnter` is invoked. Inside this function, we add a new to-do to the input control value. 

This part isn’t relevant to our example so I skipped it, but assigning the `inputValue` to the reactive variable `recentTodo` is crucial because we use `recentTodo` as the input of the composable:

```typescript
>useTitleSimple(recentTodo)
```

If you are familiar with React's custom Hooks, composables should look very similar. You can find many examples of reusable composables in the [VueUse library](https://vueuse.org/), which is a good learning resource for best practices regarding composable inputs and return values, and provides the documentation and source code for composables for your study. 

Speaking of good practices, let's discuss a couple of them in the next section.

### Composables with flexible inputs and dynamic outputs

Below is a more advanced example of the previous composable. It's also part of `useTitle.ts` and can be used by a default import:

```typescript
// composables/useTitle.ts
import { type MaybeRef, watchEffect, ref } from "vue";
type Options = {
  observe?: boolean;
  prefix?: string;
};
export default (title: MaybeRef<string>, options?: Options) => {
  const titlePrefix = options?.prefix ?? "";
  const observe = options?.observe ?? true;
  const titleRef = ref(title);
  if (observe) {
    watchEffect(() => {
      document.title = titlePrefix + titleRef.value;
    });
  } else {
    document.title = titlePrefix + titleRef.value;
  }
  return {
    setTitle: (newTitle: string) => {
      document.title = titlePrefix + newTitle;
    },
    titleRef,
  };
};
```

Here is [how we’d accomplish this in React](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/hooks/useTitle.js). 

This example is also useful for demonstrating the Reactive API’s utility functions. First of all, we make the composable more flexible by allowing a raw `string` value or a `ref` of type `string` by typing the argument as `MaybeRef<string>`. 

We can entirely omit the second argument of the composable, `options`, because of `?`. If you omit it completely, both default values get assigned in line 8 and 9\. If an argument is present, the `observe` and `prefix` properties can also be omitted. 

Therefore, we can provide default values if options are not provided. The `titlePrefix` variable defaults to an empty `string` and the `observe` flag is set to `true`. 

With the first argument, `title`, we create a `ref` named `titleRef`. If `title` is already a `ref`, this value is returned; otherwise a new `ref` is created. 

If the `observe` config flag is set to `true`, we set up a `watchEffect` to adapt the document title whenever `titleRef`'s value gets changed. However, because you can have `observe` set to `true` but also provide a raw `string`, we return the `titleRef` as a composable output. 

If `observe` is falsy, we don't set up a watcher and instead assign the initial value in the `else` condition. Then, the consumer of the composable has to use a callback function, `setTitle`, to adapt the document title. 

Because of this, we can use the composable in `TodoInput` in multiple ways:

```typescript
<!-- variant 1 -->
<script setup lang="ts">
  /* ... */
  const recentTodo = ref<string>(document.title);
  /* title: ref<string>, observe: true, prefix: 🚀 */
  useTitle(recentTodo, { prefix: "🚀 " });

  /* ... */
  const onEnter = (evt: Event) => {
    /* ... */

    // the watcher gets triggered
    recentTodo.value = inputValue.value;
  });
</script>
<!-- variant 2 -->
<script setup lang="ts">
  /* ... */
  /* title: raw string, observe: true, prefix: "" */
  const { titleRef } = useTitle(document.title);
  // use returned `ref` from the composable
  const recentTodo: Ref<string> = titleRef;
  /* ... */
  const onEnter = (evt: Event) => {
    /* ... */

    // the watcher gets triggered
    recentTodo.value = inputValue.value;
  });
</script>
<!-- variant 3 -->
<script setup lang="ts">
  /* ... */
  /* title: raw string, observe: false, prefix: "" */
  const { setTitle } = useTitle(document.title, { observe: false });

  /* ... */
  const onEnter = (evt: Event) => {
    /* ... */

    // we need to use the returned setter to update
    setTitle(inputValue.value);
  });
</script>
```

These example composables show that it’s a good approach to list your composable options as optional objects after passing the required arguments. You should also provide default values in such a way that your composable can handle any constellation of inputs.

### Similarities and differences between React Hooks and Vue composables

If you have experience with React Hooks, you will already find most of the underlying principles of composables familiar. In contrast to React, there are no [Rules of Hooks](https://react.dev/warnings/invalid-hook-call-warning) that force you to use composables at the top level of a Vue component. You can use them in conditional code, but this is most likely not a good idea. Of course, you can also use composables inside of other composables. They can even be used outside of components! 

With React Hooks, you rely on `useState` for managing state, and with composables you make use of the Reactivity API. 

`watch` and `watchEffect` are Vue's equivalent to React's `useEffect`, which allow you to re-evaluate composables and invoke side effects. Composables can also leverage lifecycle Hooks, such as `onMounted`. We’ll discuss `watchers` and lifecycle Hooks in more depth in the next section.

## Rendering and component lifecycles with Vue composables

Though we’ve been able to draw some similarities between React and Vue so far, their conceptual foundations actually differ drastically, which will become particularly evident in this section. 

If you are familiar with the [lifecycle methods that are part of class-based React components](https://blog.logrocket.com/react-lifecycle-methods-tutorial-examples/), then the learning curve to understanding Vue's lifecycle hooks won’t be steep.

### Hooking into lifecycle stages and reacting to state changes

In our previous section example, we used the `onMounted` Hook to focus the input field to create to-dos after the initial render of `TodoInput.vue`:

```javascript
<script setup lang="ts">
  // TodoInput.vue
  import { onMounted } from "vue";
  // ...
  onMounted(() => {
    // ...
  });
// ...
</script>
```

Here’s [the React example](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/TodoInput.jsx). 

The conceptual counterpart in functional React that most closely mirrors this is to use `useEffect` with an empty dependency array:

```javascript
  // TodoInput.jsx
  useEffect(() => {
    // ...
  }, []);
```

There exist other [lifecycle hooks](https://vuejs.org/api/composition-api-lifecycle.html). For example, unmounting in Vue could look like this:

```javascript
<script setup lang="ts">
  import { onUnmounted } from "vue";
  onUnmounted(() => {
    // do cleanup
  });
</script>

// TodoInput.jsx
  useEffect(() => {
    // ...
    return () => {
      // do cleanup
    }
  }, []);
```

With [`onUpdated`](https://vuejs.org/api/composition-api-lifecycle.html#onupdated), you can invoke a callback after Vue updates the DOM tree; there is no meaningful React counterpart to this.

### Watching state changes and performing side effects

Conceptually, there are similar use cases for the above when watching state changes and performing side effects with both React and Vue, but they also differ greatly. We already saw one example with the `useTitle` Hook or composable, respectively:

```typescript
  // useTitle.ts (Vue)
  watchEffect(() => {
    /* ... */ = title.value;
  });
  // useTitle.js (React)
  useEffect(() => {
    // ...
  }, [title]);
```

In Vue, we use [watchers](https://vuejs.org/guide/essentials/watchers.html) like `watchEffect` to analyze the body of the callback function and is invoked for every reactive variable (`title`). 

With React, we have to add a state variable (`title`) to `useEffect`'s dependency array to be invoked on every state update. 

If we need to get the previous value of a Vue state, we can also use [`watch`](https://vuejs.org/api/reactivity-core.html#watch):

```typescript
// useTitle.ts
export default (title: MaybeRef<string>, options?: Options) => {
  // ...
  const titleRef = ref(title);
  if (observe) {
    // ...
    watch(titleRef, (newTitle, oldTitle) => {
      console.log(`watch new: ${newTitle} old: ${oldTitle}`);
    });
  } 
  // ...
}
```

The difference between `watch` and `watchEffect` is that we explicitly tell Vue which reactive variables to watch. In this case, we watch `titleRef`, a `ref` of type `string`. The second argument is a callback that gets the raw values of the new and old states. 

[Vue lifecycle hooks](https://blog.logrocket.com/introduction-vue-lifecycle-hooks/) can be used in separate functions outside of the `<script setup>` block, such as inside composables. However, you must invoke the callback function synchronously — if you don't use a `<script setup>` component approach, make sure that the call stack originates from within the setup phase (i.e., from `setup()`).

### Component rendering <a name="component-rendering">

Let's use the `onUpdated` hook to find out whenever Vue updates the DOM of a component. In addition, I'm interested to see how often the "setup phase", i.e., the `<script setup>` section, is traversed by Vue. 

As we know, React runs the function, which constitutes the component, on every state update. I added a couple of `console.log`s to the components like the following example ([`Todos.jsx`](https://github.com/doppelmutzi/companion-project-vue3-for-react-devs/blob/main/react/src/Todos.jsx)):

```javascript
// Todos.jsx
const Todos = () => {
  console.log("render <Todos />");
  // ...
}

<!-- Todos.vue -->
<script setup lang="ts">
  import { onUpdated } from "vue";
  console.log("render <Todos />");
  onUpdated(() => {
    console.log("updated <Todos />");
  });
  // ...
</script>
```

This is the result of the Vue app after loading: ![The initial renders of our Vue app](http://blog.logrocket.com/wp-content/uploads/2023/07/initial-renders-vue-app.png) And this is how it looks in React: ![The initial renders of our React app](http://blog.logrocket.com/wp-content/uploads/2023/07/initial-renders-react-app.png) 

So far, these apps look the same. The interesting part is how the two technologies differ throughout user interactions. 

I loaded both apps and cleaned the console output. Let's look at the Vue app first: ![The renders and DOM updates recorded in our Vue app](http://blog.logrocket.com/wp-content/uploads/2023/07/renders-dom-updates-vue-app.gif) This recording reveals that Vue runs the `setu``p` phase only once — so components are rendered only once. No re-renders occur in this example, so only new components were rendered when I added a new to-do item. While hovering over the to-do items, a lot of update events were fired to adjust the DOM trees. 

What about in React? Let’s see: ![The renders and DOM updates recorded in our React app](http://blog.logrocket.com/wp-content/uploads/2023/07/renders-dom-updates-react-app.gif) After pressing **Enter** to create a to-do, a couple of components re-rendered. The same happened when I hovered over the to-do items. 

A sophisticated performance analysis of both technologies is out of scope here, but I believe that React’s component re-renders take a larger hit on performance because DOM updates take place behind the scenes. Under normal circumstances, Vue does not re-render components after the initial rendering cycle. 

For small and mid-size React projects, this is not much of a problem. You can opt out of these re-renders in React with Hooks like `useMemo` or `useCallback`, or by choosing wisely between `useState` or `useRef`. All together, though, this adds some more complexity to projects and might raise the entry barrier for developers who are new to React. 

Arek Nawo describes this difference as the ["in vs. out" principle](https://areknawo.com/vue-composition-api-vs-react-hooks-the-core-difference/), in the sense that React devs have to opt _out_ of re-renders (e.g., with `useMemo` or dependencies in Hooks like `useEffect`) and with Vue, devs need to opt _in_ to redraws (e.g., with watchers).

## Conclusion

React and Vue are two different animals, but they are both built to realize the same frontend development use cases. Though the fundamental design concepts behind React and Vue differ greatly, I believe they each enable a quick entry into a new framework, especially when you compare one technology with the other conceptually, from a higher abstraction level.

---

## Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, [try LogRocket](https://lp.logrocket.com/blg/vue-signup).

[![LogRocket Signup](https://files.readme.io/00591d0-687474703a2f2f692e696d6775722e636f6d2f6a3049327856572e706e67.png)](https://lp.logrocket.com/blg/vue-signup)

[LogRocket](https://lp.logrocket.com/blg/vue-signup) is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - [Start monitoring for free](https://lp.logrocket.com/blg/vue-signup).