My Favorite Vue.js Feature

· 940 words · 5 minute read

Sharing state down the DOM ##

Sharing state in a component based framework is a common task. Every application I have ever build requested a list of data from a JSON-API and rendered each item by sharing its data with a child component.

The React way ###

Passing data down the DOM from the parent to a child component, is encouraged to be done using component properties. (see Sharing State Between Components)

 1// TitleList.jsx
 2import { useState } from "react";
 3
 4function Title({ title }) {
 5  return <span>{title}</span>;
 6}
 7
 8export default function TitleList() {
 9  const [titles, updateTitles] = useState(["title1", "title2", "title3"]);
10  return (
11    <div>
12      {titles.map((t) => (
13        <Title key={t} title={t} />
14      ))}
15    </div>
16  );
17}

Using react one can simply define a component named Title accepting a singular argument called title. This component is then used to render the list of titles by mapping over all values and passing the current title to the children via the title property.

Modifying the list of titles can be done by invoking the updateTitles and spreading the previous values as well as the new value into a new array:

 1// TitleList.jsx
 2import { useState } from "react";
 3
 4function Title({ title }) {
 5  return <span>{title}</span>;
 6}
 7
 8export default function TitleList() {
 9  const [titles, setTitles] = useState(["title1", "title2", "title3"]);
10  const [newTitle, setNewTitle] = useState("");
11  return (
12    <>
13      <div>
14        {titles.map((t) => (
15          <Title key={t} title={t} />
16        ))}
17      </div>
18      <input
19        type="text"
20        placeholder="new title"
21        onChange={(event) => setNewTitle(event.target.value)}
22      />
23      <button onClick={() => setTitles([...titles, newTitle])}>Add</button>
24    </>
25  );
26}

How Vue.js does it ###

Using Vue.js the example can be written slightly more concise and intuitive:

 1// Title.vue
 2<script setup lang="ts">
 3const props = defineProps<{ title: string }>();
 4</script>
 5<template>
 6  <span>{{ title }}</span>
 7</template>
 8
 9// TitleList.vue
10<script setup lang="ts">
11import { ref } = from "vue";
12import Title from "./Title.vue";
13
14const titles = ref<Array<string>>([]);
15const newTitle = ref<string>("");
16</script>
17<template>
18  <div>
19    <Title v-for="t in titles" :title="t" />
20  </div>
21  <input type="text" placeholder="new title" v-model="newTitle" />
22  <button @click="titles.push(newTitle)">Add</button>
23</template>

This is logically the same, vue does however allow for direct state modification- thus omitting the need for allocating a new array each time we add a new title via the spread syntax ([...prevElements, newElement]).

Sharing state up the DOM ##

Here up the DOM refers to children modifying and sharing these updates with their parents. Doing so directly is often not possible or discouraged by frameworks such as React.js (However react doesn’t even allow direct modification of state inside the current component).

React.js - Passing functions ###

When using React the parent often passes a function in which the parents state is modified to the children via a prop:

 1import { useState } from "react";
 2
 3function Title({ title, deleteFunction }) {
 4  return (
 5    <div>
 6      <span>{title}</span>
 7      <button onClick={() => deleteFunction(title)}>Delete</button>
 8    </div>
 9  );
10}
11
12export default function TitleList() {
13  const [titles, updateTitles] = useState(["title1", "title2", "title3"]);
14  const [newTitle, setNewTitle] = useState("");
15  function deleteByTitle(title) {
16    updateTitles(titles.filter((t) => t !== title));
17  }
18  return (
19    <>
20      <div>
21        {titles.map((t) => (
22          <Title key={t} title={t} deleteFunction={deleteByTitle} />
23        ))}
24      </div>
25      <input
26        type="text"
27        placeholder="new title"
28        onChange={(event) => setNewTitle(event.target.value)}
29      />
30      <button onClick={() => updateTitles([...titles, newTitle])}>Add</button>
31    </>
32  );
33}

The above example can be played with and ran here:

Vue.js - emitting events ###

In the vue world we define a set of events we want to emit in the child component, the parent component is then able to listen to these events and act on them. (See Event Handling)

 1// Title.vue
 2<script setup lang="ts">
 3const props = defineProps<{ title: string }>();
 4const emit = defineEmits(["delete"])
 5</script>
 6<template>
 7  <span>{{ title }}</span>
 8  <button @click="emit('delete')">Delete</button>
 9</template>
10
11// TitleList.vue
12<script setup lang="ts">
13import { ref } from "vue";
14import Title from "./Title.vue";
15const titles = ref<Array<string>>([]);
16const newTitle = ref<string>("");
17</script>
18<template>
19  <div>
20    <Title
21        v-for="t in titles"
22        :title="t"
23        @delete="titles = titles.filter((title) => t !== title)" />
24  </div>
25  <input type="text" placeholder="new title" v-model="newTitle" />
26  <button @click="titles.push(newTitle)">Add</button>
27</template>

Tip

The above example can be played with and ran here;

vue-example

Event listeners and emitting events is a powerful tool for simplified state management and I use both a lot when developing with Vue.js.

Real world example ##

One can build components such as modals and confirmation prompts with custom events and the ability to react to actions taken by the user. Lets now take a look at one such example of a customisable modal:

 1// Modal.vue
 2<script setup lang="ts">
 3const props = defineProps<{
 4    title: string;
 5    description: string;
 6    events: Array<{ title: string; name: string }>;
 7}>();
 8const emit = defineEmits();
 9</script>
10<template>
11    <div>
12        <h3>{{ title }}</h3>
13        <p>{{ description }}</p>
14    </div>
15    <div>
16        <button
17            v-for="event in events"
18            @click="emit(event.name)"
19        >
20        {{ event.title }}
21        </button>
22    </div>
23</template>
24
25// App.vue
26<script setup>
27import {ref} from "vue";
28import Modal from "./Modal.vue";
29const eventOutput = ref("");
30</script>
31
32<template>
33  <Modal
34    title="Confirmation"
35    description="Confirm you want to do that"
36    :events="[
37        {title: 'Yes', name: 'yes'},
38        {title: 'No', name: 'no'}
39        ]"
40    @yes="eventOutput = 'yes'"
41    @no="eventOutput = 'no'"
42    />
43    <span>{{ eventOutput }}</span>
44</template>

Clicking one of the generated buttons changes the eventOutput value to either yes or no:

vue-modal

Tip

Read more about events, event handling and custom events here.