diceline-chartmagnifiermouse-upquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

Vuex composition helpers utility package

One big advantage of the Composition API over the Options API is that it lets us group our variables and methods how we want. Most of the time we group them together by feature/functionality, as they use/call one another and it's convenient to have them grouped together this way.

For all apps where we use Vuex for state management, we probably take advantage of the mapState, mapGetters, mapActions and mapMutations binding helpers. Unfortunately, this does not work as expected with the Composition API. But there is also a way of how we can replicate this behavior and also maintain the structure of our components as described at the beginning as an advantage.

We have to install the vuex-composition-helpers utility package:

npm i vuex-composition-helpers@next

and use it as follows in our component:

<script setup lang="ts">

import { useState, useActions, useGetters, useMutations } from 'vuex-composition-helpers'

const { globalMessage, onlineUsers } = useState(['globalMessage', 'onlineUsers'])
const { getGlobalMessage } = useGetters(['getGlobalMessage'])
const { UPDATE_MESSAGE, ADD_USER } = useMutations(['UPDATE_MESSAGE', 'ADD_USER'])
const { updateMessage, addOnlineUser } = useActions(['updateMessage', 'addOnlineUser'])


</script>

DefineEmits and DefineProps compiler macros with default values in TypeScript

This is how we:


a. use the defineEmits() and defineProps() compiler macros with Composition API's Script Setup and TypeScript

b. pass default values to our component's props

<script setup>
const props = withDefaults(
  defineProps<{
    message: string, 
    isActive: boolean
  }>(), 
  {
    message: 'Default message', 
    isActive: false
  }
);

const emit = defineEmits<{
  (e: "customEventName", payload?: object) : void
}>();
</script>

Script setup syntactic sugar for the Composition API in SFCs

Problem: When using the Composition API, we need to return all (reactive) variables from the setup() method in order to use them in the template section. This can make components huge, harder to read and maintain and also cause unwanted bugs if we forget to return any variables used in the template. More than that, we can use variables only by accessing their "value" property.

This is the basic structure of a component that we want to improve:

<script>
//Long list of imports 

import Child1 from 'child1-location';
import Child2 from 'child2-location';
import { ref } from 'vue';

export default defineComponent({
  name: 'CustomComponent',
  components: { Child1, Child2 },
		
  setup() {
	const variable1 = ref(1);
	const variable2 = ref(2);
	//...
	const variable100 = ref(100);

	//altering variables
	variable1.value = 'new value for variable1';

	return {
		variable1,
		variable2,
		.
		.
		.
		variable100,
	}
  }
});
</script>

<template>
	<Child1 />
	<Child2 />
	{{ variable1 }}
	...
	{{ variable100 }}
</template>

Solution: The structure and performance of this component can be improved by using the Script Setup syntactic sugar. 
The Script Setup is a compile-time syntactic sugar that is recommended to be used in SFCs with the Composition API. Let's rewrite the component from above using the Script Setup:

<script setup> 
	
import Child1 from 'child1-location';
import Child2 from 'child2-location';
import { ref } from 'vue';
	
const variable1 = ref(1);
const variable2 = ref(2);
//...
const variable100 = ref(100);

variable1 = 'new value for variable1';
</script>

<template>
        <Child1 />
	<Child2 />
	{{ variable1 }}
	...
	{{ variable100 }}
</template>

Important differences and advantages: 



  1. We don't need to return any variables from our script in order to use them in the template, reducing the number of lines and increasing readability and operability
  2. We don't need to use .value to access and modify reactive variable values
  3. We can directly use imported Child Components in our template without registering them
  4. Better runtime performance due to the behind the hood implementation of the template (as a render function without an intermediate proxy)
  5. TypeScript support for defining props and emitted events (will be described in a future TIL)

File validation in vueJS with vee-validate

As we know, "v-model" does not work on an input that has a type of "file". So to validate a file is a little bit tricky.

For this validation, we will use vee-validate -> https://vee-validate.logaretm.com/v4/

To do this, you need to add a method that triggers the "on change" event on your input and to add on the "ValidationProvider" a ref.

import { ValidationProvider } from 'vee-validate/dist/vee-validate.full.esm'

<ValidationProvider ref="provider" rules="required">
 <input
 ref="file"
 type="file"
 @change="onFileAdd"
 />
</ValidationProvider>

Also you need to go and create the "onFileAdd" method , which will contain the following code:

export default {
 data() {
  return {
    fileData: '',
  }
 }

 methods: {
  async onFileAdd(e){
   const { valid } = await this.$refs.provider.validate(e)
   if (valid) {
    const file = this.$refs.file.files[0]
    this.fileData = file
   }
  }
 }
}

The "fileData" in the data object is the file that will be sent when the form is submitted.

Commit your changes. That's all.

Call function when image is loaded in VueJS using @load.

In VueJS, there is a way to find out when an image is fully loaded on a web page (when the image request is finished). We achieve this using the @load event that is available in VueJS and works in the following way:

<template>
  <div>
    <img 
      class="img__class"
      src="/photo.jpg"
      @load="onImageLoad"
     />
  </div>
</template>

export default {
  data: {
    isImageLoaded: false,
  },

  methods: {
    onImageLoad() {
      this.isImageLoaded = true
      // optional
      // console.log('Image is loaded')
    }
  }
}

This feature will come in handy if we want to render skeleton components while images are loading or if we want to implement lazy loading functionality.