Vue

Binding

Assign that value of a class or id to a variable

<div v-bind:id="dynamicId">

<!-- or -->

<div :id="dynamicId">

Listening to Events

<a v-on:click="doSomething"> ... </a>

<!-- shorthand -->
<a @click="doSomething"> ... </a>

Binding Variable Events

<a v-on:[eventName]="doSomething"> ... </a>

<!-- shorthand -->
<a @[eventName]="doSomething"> ... </a>

Reactivity

<button @click="count++">
  {{ count }}
</button>

Computed/Derived Properties

  • Computed properties only get recomputed when their reactive dependencies change
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// a computed ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

Conditional Rendering

v-if vs. v-show. v-show will always be in the dom, just with the display property visible or not

v-if is more expensive to toggle, whereas v-show is more expensive to render initially

Logic

<li v-for="item in items">
  {{ item.message }}
</li>

<!-- or -->

<li v-for="{ message } in items">
  {{ message }}
</li>

It's recommended to always use a :key

Listeners/Modifiers

<!-- the submit event will no longer reload the page -->
<form @submit.prevent="onSubmit"></form>

<!-- only call `submit` when the `key` is `Enter` -->
<input @keyup.enter="submit" />

Form Input Bindings v-model

<!-- instead of -->
<input
  :value="text"
  @input="event => text = event.target.value">

<!-- we can do -->
<input v-model="text">

<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
<AssignmentTags
  v-model:currentTag="currentTag"
/>

<!-- in AssingmentTags -->

<template>
  <button
    @click="$emit('update:currentTag', tag)"
  >

  </button>
</template>
props: {
  currentTag: String
}

Throttling and Debouncing

Throttle requests to every 500ms (using lodash

<script setup>
watch(search, throttle(function (value) {
  Inertia.get('/users')
}, 500))
</script>

Debounce - does the request after you've stopped typing for 500ms

<script setup>
watch(search, debounce(function (value) {
  Inertia.get('/users')
}, 500))
</script>

Saving in Local Storage

<script setup>
let food = ref(localStorage.getItem("food"));

function write(key, val) {
  localStorage.setItem(key, val)
}
</script>


<template>
  <input type="text" v-model="food" @input="write('food', food)">
</template>

or as a compostable

import { ref, watch } from "vue";

export function useStorage(key) {
  storedValue = localStorage.getItem(key);
  let value = ref(storedValue);

  function write() {
    localStorage.setItem(key, value);
  }

  watch(value, () => {
    if (value.value === "") {
      localStorage.removeItem(key);
    }

    write(key, value.value);
  });

  return value;
}

Components

onMounted() - runs after the component has finished rendering

Slots

<!-- The component -->
<button class="fancy-btn">
  <slot />
</button>

<!-- Usage -->
<FancyButton>
  Click me! <!-- slot content -->
</FancyButton>

Computed Properties (Watchers)

to watch a variable and call code whenever it changes

<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.includes('?')) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" :disabled="loading" />
  </p>
  <p>{{ answer }}</p>
</template>

Emitting Messages to Parents

<!-- Checkbox Component -->
<script setup lang="ts">
  import { computed } from 'vue'

  // The update prefix is only used when you want two-way-binding in the parent component to the value of something in the child
  const emit = defineEmits(['update:checked'])

  const props = defineProps<{
    checked: boolean
    value?: any
  }>()

  const proxyChecked = computed({
    get() {
      return props.checked
    },

    set(val) {
      // emit a message called update set to the value of checked
      emit('update:checked', val)
    },
  })
</script>

<!-- In the parent - bind the value of the form to -->
<!-- Vue automatically includes the update: prefix when we're using v-model -->
<Checkbox name="remember" v-model:checked="form.remember" />

<!-- Example 2 -->
<textarea @keyup="emit('update:modelValue', e.target.value)"/>

Emitting the Value to the Parent

In the child:

<script setup>
const emits = defineEmits(['update'])
const internalValue = ref(42)

// Expose the internal value and allow parent to request it
const getInternalValue = () => {
  emits('update', internalValue.value)
}
</script>

<template>
  <div>
    <p>Internal value: {{ internalValue }}</p>
    <button @click="getInternalValue">Send to Parent</button>
  </div>
</template>

In the parent:

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const receivedValue = ref(0)

const handleUpdate = (value) => {
  receivedValue.value = value
}
</script>

<template>
  <div>
    <h2>Parent Component</h2>
    <p>Received value: {{ receivedValue }}</p>
    <ChildComponent @update="handleUpdate" />
  </div>
</template>

v-model Binding to the Child's State

In the child

<script setup>
const value = defineModel()
</script>

<template>
  <div>
    <input v-model="value" type="number">
    <p>Child value: {{ value }}</p>
  </div>
</template>
<script setup>
const parentValue = ref(42)
</script>

<template>
  <div>
    <p>Parent value: {{ parentValue }}</p>
    <ChildComponent v-model="parentValue" />
  </div>
</template>

Conditional Styling

<button
  :class="{'bg-gray-200': type === 'gray'}">

Inertia Niceties

Displaying form errors

<script setup>
defineProps({
  errors,
});
</script>

<template>
  <input v-model="form.email" required/>
  <div v-if="errors.email" v-text="errors.email"></div>
</template>

Forms

<script setup>
const loginForm = useForm({
    email: '',
    password: ''
});

const signupForm = useForm({
    name: '',
    email: '',
    password: '',
    password_confirmation: ''
});
</script>

<form @submit.prevent="loginForm.post(route('login'))" :disabled="loginForm.processing">
    <input v-model="loginForm.email">
    <input v-model="loginForm.password" type="password">
</form>

<form @submit.prevent="signupForm.post(route('register'))" :disabled="signupForm.processing">
    <input v-model="signupForm.name">
    <input v-model="signupForm.email">
    <input v-model="signupForm.password" type="password">
    <input v-model="signupForm.password_confirmation" type="password">
</form>

Composables

The naming convention is to prefix the names with use

// composables/useFlash.js
export function useFlash.js

Dependency Injection

provide('key', object)

// a few levels deep
let obj = inject('key')

Pinia Stores

export let useTeamStore = defineStore("team", {
  state: () => ({
    name: "",
    sport: 0,
  }),

  actions: {
    async init() {
      let r = await import("file.json");

      this.$state = r.default;
    },
  },
});