/ Gists / Vue.js

Gists - Vue.js

On gists

Smarter if

JavaScript Vue.js

code.js #

// Noob
const eventHandler = e => {
  if (e.key === 'Escape') {
    this.closeGallery()
  }
  if (e.key === 'ArrowLeft') {
    this.prevMedia()
  }
  if (e.key === 'ArrowRight') {
    this.nextMedia()
  }
}

// Profi
const eventHandler = e => {
  const keyActions = {
    Escape: closeGallery,
    ArrowLeft: prevMedia,
    ArrowRight: nextMedia
  }

  keyActions[e.key]?.()
}

On gists

use cases - computed properties

Vue.js

shopping-cart.js #

<template>
  <div>
    <ul>
      <li v-for="item in cartItems" :key="item.id">
        {{ item.name }} - {{ item.price }} - Quantity: {{ item.quantity }}
      </li>
    </ul>
    <p>Total Price: {{ totalPrice }}</p>
    <p>Total Quantity: {{ totalQuantity }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      cartItems: [
        { id: 1, name: 'Item 1', price: 10, quantity: 2 },
        { id: 2, name: 'Item 2', price: 15, quantity: 1 },
        { id: 3, name: 'Item 3', price: 20, quantity: 3 }
      ]
    };
  },
  computed: {
    totalPrice() {
      return this.cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
    },
    totalQuantity() {
      return this.cartItems.reduce((total, item) => total + item.quantity, 0);
    }
  }
};
</script>

On gists

Component props stealing

Vue.js

demo.js #

// my version - inspired by https://michaelnthiessen.com/stealing-prop-types/

// iconDefault.js
export const iconDefaults = {
  size: 'medium',
  shape: 'circle',
  icon: 'default',
  animation: 'none'
};


<!-- Icon.vue -->
<script setup>
import { iconDefaults } from './iconDefaults.js';

const props = defineProps({
  size: { type: String, default: iconDefaults.size },
  shape: { type: String, default: iconDefaults.shape },
  icon: { type: String, default: iconDefaults.icon },
  animation: { type: String, default: iconDefaults.animation }
});
</script>

<!-- Panel.vue -->
<script setup>
import Icon from './Icon.vue';
import { iconDefaults } from './iconDefaults.js';
import { computed } from 'vue';

const props = defineProps({
  heading: String,
  withIcons: Boolean,
  iconConfig: {
    type: Object,
    default: () => ({ ...iconDefaults })
  }
});

const finalIconConfig = computed(() => {
  return { ...iconDefaults, ...props.iconConfig };
});
</script>

On gists

Error Boundary aka Nuxt

Vue.js

ErrorBoundary.vue #

<!--ErrorBoundary.vue-->
<!-- https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-error-boundary-component/ -->
<script setup>
import { ref, computed } from 'vue'

// create an reactive container to store the potential error
const error = ref()

// using Vue's build in lifecycle hook
// listen for errors from components passed to the default slot
onErrorCaptured(err => {
  // set the reactive error container to the thrown error
  error.value = err

  // return false to prevent error from bubbling further
  // (this is optional, if you have a top level error reporter catching errors
  // you probably don't want to do this.
  // Alternatively you could report your errors in the boundary and prevent bubble
  return false
})

// create a way to clear the error
function clearError() {
  error.value = null
}

// provide the error and the clear error function to the slot
// for use in consuming component to display messaging
// and clear the error
const slotProps = computed(() => {
  if (!error.value) return {}
  return { error, clearError }
})

// if there's an error show the error slot, otherwise show the default slot
const slotName = computed(() => (error.value ? 'error' : 'default'))
</script>
<template>
  <slot :name="slotName" v-bind="slotProps"></slot>
</template>

On gists

Vue advanced

Vue.js

advanced.js #

/*
onScopeDispose - Podobně jako onWatcherCleanup, ale obecnější. 
Zavolá se, když je aktuální reaktivní efektový scope (effect scope) ukončen. 
Je to užitečné pro čištění zdrojů v jakémkoliv reaktivním kontextu:
*/

// 1
import { onScopeDispose, effectScope } from 'vue'

// Vytvoření izolovaného scope
const scope = effectScope()

scope.run(() => {
  // Kód uvnitř scope
  
  onScopeDispose(() => {
    // Tento kód se zavolá při scope.stop()
  })
})

// Později můžete ukončit scope
scope.stop()




// 2) 
const scope = effectScope()

scope.run(() => {
  const state = reactive({ count: 0 })
  const double = computed(() => state.count * 2)
  watch(() => state.count, (count) => console.log(count))
})

// Později ukončí všechny reaktivní efekty
scope.stop()

On gists

Watch - advanced

Vue.js

watch.js #

// 1) cleanup when watch is ended
import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const { response, cancel } = doAsyncWork(newId)
  // `cancel` is called if `id` changes or the component unmounts
  onWatcherCleanup(cancel)
})



// 2) better watch with desctruct
const { pause, resume, stop } = watch(source, (newVal, oldVal) => {
  // Watch logic
});

// Pause watching
pause();

// Resume watching
resume();




// 3 watch logic, not only true/false ...
watch(reactiveObject, (newVal, oldVal) => {
  // Watch logic
}, { deep: 2 });

On gists

Better Vue components with TS

JavaScript Typescript Vue.js

better-vue-with-components.vue #

<!-- 1. PROPS  -->

<!-- JS -->
<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  foo: { type: String, required: true },
  bar: Number,
});

// props.foo is a string
// props.bar is a number or undefined
</script>


<!-- TS -->
<script setup lang="ts">
const props = defineProps<{
  foo: string;
  bar?: number;
}>()
</script>


<script setup lang="ts">
interface Props {
  foo: string;
  bar?: number;
}

const props = defineProps<Props>()
</script>



<!-- 2. EMITS  -->
<!-- JS -->
<script setup>
const emit = defineEmits(['change', 'update'])
</script>

 <!-- TS -->
 <script setup lang="ts">
const emit = defineEmits<{
  change: [id: number]
  update: [value: string]
}>()
</script>



<!-- 3. Typing Ref and Reactive Data => Inference -->
<script setup>
import { ref } from 'vue';

const count = ref(0); 
count.value = 'string';  // error, inference, we expect number
</script>


<!-- 4. Typing Server Responses -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';

interface User {
  id: number;
  name: string;
  email: string;
}

const userData = ref<User | null>(null);

onMounted(async () => {
  const response = await fetch('https://api.example.com/user');
  const data: User = await response.json();
  userData.value = data; // TypeScript ensures data usages match the User interface
});
</script>


<!-- 5. Typing Computed Data -->
import { ref, computed } from 'vue'

const count = ref(0)
// inferred type: ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')

// or explicitely define return type
const double = computed<number>(() => {
  // type error if this doesn't return a number
})



<!-- 6. Typing Scoped Slots-->
<template>
  <slot :msg="message"></slot>
</template>

<script setup lang="ts">
const message = 'Hello, Vue!';

const slots = defineSlots<{
  default: (props: { msg: string }) => any;
}>()
</script>


<!-- 7. Typing Template Refs -->
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue';

const myInput = useTemplateRef('my-input');

onMounted(() => {
  myInput.value?.focus(); 
});
</script>

<template>
  <input ref="my-input" />
</template>



<!-- 8. Typing Provide/Inject -->
<!-- ParentComponent.vue -->
<script setup lang="ts">
import { provide } from 'vue';

const theme = 'dark';
provide('theme', theme);
</script>

<!-- ChildComponent.vue -->
<script setup lang="ts">
import { inject } from 'vue';

const theme = inject<string>('theme'); // Inject with expected type
// TypeScript ensures that 'theme' is of type string
</script>


<!-- 9. Generics -->
<script setup lang="ts" generic="T">
defineProps<{
  items: T[];      // Array of items of type T
  selected: T;     // Single selected item of type T
}>()
</script>


<!-- 10. Typed Composables -->
// useUser.ts
import { ref } from 'vue';

interface User {
  id: number;
  name: string;
  age: number;
}

export function useUser() {
  const user = ref<User | null>(null);

  function fetchUser(id: number) {
    // Fetching user logic
    user.value = { id, name: 'John Doe', age: 30 };
  }

  return { user, fetchUser };
}

On gists

Ben Hong - One object to rule them all

Vue.js

obj.vue #

<!-- https://www.vuemastery.com/courses/component-design-patterns/one-object-to-rule-them-all -->

<!-- To heavy , to complicated, all props we dont need ...-->
<template>
  <main>
    <Component 
      v-for="content in apiResponse"
      :key="content.id"
      :is="content.type"
      :article-title="content.title"
      :article-content="content.body"
      :ad-image="content.image"
      :ad-heading="content.heading"
      @click="content.type === 'NewsArticle' ? openArticle : openAd"
      @mouseover="content.type === 'NewsArticle' ? showPreview : trackAdEvent"
    />
  </main>
</template>


<!--Much better -->
<template>
  <main>
    <Component 
      v-for="content in apiResponse"
      :key="content.id"
      :is="content.type"
      v-bind="feedItem(content).attrs"
      v-on="feedItem(content).events"
    />
  </main>
</template>

<script>
export default {
  methods: {
    feedItem(item) {
      if (item.type === 'NewsArticle') {
        return {
          attrs: {
            'article-title': item.title,
            'article-content': item.content
          },
          events: {
            click: this.openArticle,
            mouseover: this.showPreview
          }
        }
      } else if (item.type === 'NewsAd') {
        return {
          attrs: {
            'ad-image': item.image,
            'ad-heading': item.heading
          },
          events: {
            click: this.openAd,
            mouseover: this.trackAdEvent
          }
        }
      }
    }
  }
}
</script>

On gists

Content from Slot

Vue.js

TheTest.vue #

<script setup>
import   { useSlots, onMounted, ref } from "vue";

const test = ref()

onMounted(() => {
// 1
console.log(test.value.children[0])
 
 // 2 nebo bez refu primo ze slotu useSlots().default()[0].el
})
</script>

<template>
  <!-- nejde ref umistit primo na slot, nutno to takto obalit, nebo viz druhy zpusob -->
  <div ref="test"> 
  <slot />
  </div>
</template>

On gists

Form composable

Vue.js

Old.js #

// https://cdruc.com/form-composable

const router = useRouter();

const form = ref({
  title: "",
  content: "",
});

const isProcessing = ref(false);
const error = ref(null);

async function handleSubmit() {
  if (isProcessing.value) return;

  error.value = null;
  isProcessing.value = true;

  try {
    await axios.post("/api/posts", form.value);
    await router.push("/");
  } catch (err) {
    error.value = err;
  }

  isProcessing.value = false;

  if (error?.status === 403) {
    await router.replace("/login");
  }
}