# Vue JS ### Basic Frontend Framework Concepts - **SPA** or Single Page Application is a type of web application that only contains a single html file. The idea is to make it feel more like a native app by updating the UI with JavaScript in the Browser instead of requesting a new HTML page on each request from the server. - **State** is a variable in a component that contains data, on change, should trigger the re-rendering of UI. - **Props** are like the parameters passed to a function from its parents. These are useful if you want to pass state of a parent component to its children components. You can do it through props. - In **Component-based Architecture** the UI is divided into small UI components that connect with each other to create a single Page. - ... - **When to create Components?** - For Reusability: For example an input can be used multiple times. So its code should be extracted into a separate component to increase reuseability. - For separation of concern: For example it makes sense to extract a Form Component into a standalone component than the rest of the page because the primary functin of this cmponenet is to take data from user and perform some action on it while rest of elements in the Page might just be for displaying the data. #### References: [Intro to Vue - VueMastery](https://www.vuemastery.com/courses/intro-to-vue-3/intro-to-vue3) [VueJS Docs - Hands On Tutorial](https://vuejs.org/tutorial/#step-1) [Vue 3 - The Net Ninja](https://youtube.com/playlist?list=PL4cUxeGkcC9hYYGbV60Vq3IXYNfDk8At1) [VueJS Docs](https://vuejs.org/guide/essentials/application.html) [Vue Router 4 for Everyone](https://vueschool.io/lessons/introduction-to-vue-router-4) [Deep Dive into Vue's watch method (Vue's useEffect)](https://www.netlify.com/blog/2021/01/29/deep-dive-into-the-vue-composition-apis-watch-method/) ## What is Vue? Vue is a JavaScript framework for building user interfaces. It builds on top of standard HTML, CSS, and JavaScript and provides a declarative and component-based programming model that helps you efficiently develop user interfaces, be they simple or complex. ### Features of Vue **Declarative Rendering**: Vue extends standard HTML with a template syntax that allows us to declaratively describe HTML output based on JavaScript state. **Reactivity**: Vue automatically tracks JavaScript state changes and efficiently updates the DOM when changes happen. ## Single File Components (SFCs) In most build-tool-enabled Vue projects, we author Vue components using an HTML-like file format called Single-File Component (also known as *.vue files, abbreviated as SFC). A Vue SFC, as the name suggests, encapsulates the component's logic (JavaScript), template (HTML), and styles (CSS) in a single file. ```jsx <script> export default { data() { return { count: 0 } } } </script> <template> <button @click="count++">Count is: {{ count }}</button> </template> <style scoped> button { font-weight: bold; } </style> ``` ## API Styles Vue components can be authored in two different API styles: **Options API** and **Composition API**. ### Options API With Options API, we define a component's logic using an object of options such as `data`, `methods`, and `mounted`. Properties defined by options are exposed on `this` inside functions, which points to the component instance: ```jsx <script> export default { // Properties returned from data() become reactive state // and will be exposed on `this`. data() { return { count: 0 } }, // Methods are functions that mutate state and trigger updates. // They can be bound as event listeners in templates. methods: { increment() { this.count++ } }, // Lifecycle hooks are called at different stages // of a component's lifecycle. // This function will be called when the component is mounted. mounted() { console.log(`The initial count is ${this.count}.`) } } </script> <template> <button @click="increment">Count is: {{ count }}</button> </template> ``` ### Composition API **(Following this one)** With Composition API, we define a component's logic using imported API functions. In SFCs, Composition API is typically used with `<script setup>`. The `setup` attribute is a hint that makes Vue perform compile-time transforms that allow us to use Composition API with less boilerplate. For example, imports and top-level variables / functions declared in `<script setup>` are directly usable in the template. Here is the same component, with the exact same template, but using Composition API and `<script setup>` instead: ```jsx <script setup> import { ref, onMounted } from 'vue' // reactive state const count = ref(0) // functions that mutate state and trigger updates function increment() { count.value++ } // lifecycle hooks onMounted(() => { console.log(`The initial count is ${count.value}.`) }) </script> <template> <button @click="increment">Count is: {{ count }}</button> </template> ``` ## Which API to Choose? Both API styles are fully capable of covering common use cases. They are different interfaces powered by the exact same underlying system. In fact, the Options API is implemented on top of the Composition API! The fundamental concepts and knowledge about Vue are shared across the two styles. The Options API is centered around the concept of a "component instance" (`this` as seen in the example), which typically aligns better with a class-based mental model for users coming from OOP language backgrounds. It is also more beginner-friendly by abstracting away the reactivity details and enforcing code organization via option groups. The Composition API is centered around declaring reactive state variables directly in a function scope and composing state from multiple functions together to handle complexity. It is more free-form and requires an understanding of how reactivity works in Vue to be used effectively. In return, its flexibility enables more powerful patterns for organizing and reusing logic. ##### For learning purposes Go with the style that looks easier to understand to you. Most of the core concepts are shared between the two styles. ##### For production use Go with **Options API** if you are not using build tools, or plan to use Vue primarily in low-complexity scenarios, e.g. progressive enhancement. Go with **Composition API + Single-File Components** if you plan to build full applications with Vue. --- ## Intro to Vue 3 [By Vue Mastey](https://www.vuemastery.com/courses/intro-to-vue-3/forms-and-v-model-vue3) ### Vue Instance For creating a vue app, we create an instance of the Vue application and mounts it to a DOM node. Vue Instance is the heart of our Vue application. ```javascript // Creating a Vue instance const app=Vue.create({}); // it takes an options object, // where we can configure the intial values of our app. // The options object is mandatory to pass, // if there is no value to pass then just pass an empty object. ``` We can also pass our intial state in `options` using `data()`. The object returned by `data` method is the state of our application. Vue is reactive just like React. We can declare methods of our Component inside `methods` object in `options`. ```javascript const app=Vue.create({ data(){ return { count:0 } }, methods:{ increment(){ this.count+=1 } } }) ``` After creating an app, we have to mount it in DOM. ```javascript app.mount("#CssSelector"); ``` ### Data Properties The properties that are returned by `data()` method of `options` object are called data properties. These are alternatives of state from React. ### Attribute Binding We can bind attributes to our `data` using `v-bind` directive. `v-bind` is used to dynamically bind an attribute to an expression. `v-bind` create one-way data binding, from data to template. ```jsx // v-bind:attr="expr" <img v-bind:src="image" v-bind:alt="'vue-''+(1+2) product"/> ``` There is a shorthand for `v-bind` and it is `:`. ```jsx <img :src="image" :alt="'vue-'+(1+2) product"/> ``` ### Conditional Rendering <!-- We can use interpolation syntax to also display conditional values. ```jsx <p>{{inStock?"In Stock":"Out of Stock"}}</p> ``` --> We can use `v-if`, `v-else-if` and `v-else` directive to display OM nodes conditionally. ```jsx <p v-if="inStock===true">In Stock</p> <p v-else>Out of Stock</p> ``` We dont need to pair `v-else` with `v-if`. If there is only one condition to show or not show a DOM node, we can just use `v-if` only. Alternatively, we can use `v-show` to toggle visibility of a DOM node based on the condition. ```jsx <p v-if="num%2===0"> {{num}} is Even </p> <div v-show="showModal"></div> ``` `v-if` adds or removes DOM node in the DOM. `v-show` only toggles its visibillity. So it is more performant option if we have something that is being toggled on and off on screen. When `v-show` is `false`, it adds `style="dsplay:none;"` to the node. ### List Rendering For rendering lists, we use `v-for` directive. It works just like a `for of` loop. ```jsx // v-for="item in items" // v-for="(item,index) in items" // details is a state <li v-for="detail in details" :key="detail.id">{{detail.name}}</li> ``` When rendering lists, we should give each item a key so Vue can uniquely identify that item. Th key should be a static value, mostly the `id` property from the data you are rendering. ### Event Handling We can add Event Listeners to DOM nodes using `v-on` Vue directive. There is a shorthand for `v-on` too and it is `@` ```jsx // v-on:event="expr" // v-on:event="callback" // Callback in `methods` <button v-on:click="addToCart">Click Here</button> // @event="callback" <button @click="addToCart">Click Here</button> <div v-for="size in sizes" :key="size.id" @mouseover="selectSizeHandler(size.id)"> </div> ``` ### Style & Class Binding We can change styles or add and remove CSS classes dynamically to a DOM node. For that we can use attribute binding with `style` attribute. ```jsx // :style="{style:'value'}" <p :style="{backgroundColor:'red'}">Out Of Stock</p> ``` Similarly, we can bind classes using `v-bind`. ```jsx // :class="{'class-name':condition}" <button :disabled="!inStock" :class="{'btn-disabled':!inStock}"> Add To Cart </button> ``` There is a second way to add calsses dynamically: ```jsx // :class="[inStock?'':'btn-disabled']" <button :disabled="!inStock" :class="[inStock?'':'btn-disabled']"> Add To Cart </button> ``` **What happens when we already have an existing class?** When we add classes using `v-bind`, it combines already added classes with the conditional classes. ### Computed Properties Computed Properties are the properties which are calculated from state variables. For comparison, this is something for which we use `useMemo` in React. In Vue, these are cached/ memoized by default, so we dont have to use something like `useMemo` for these. For creating computed properties, we add a `computed` object in `options` and then create a method with the name of computed property. The method returns the value that is calculated. ```jsx computed:{ title(){ return this.brand+" "+this.title; } } ``` ### Components & Props Components are building blocks of an application. An application is split into components. Props are the parameters passed to a child component from parent component. Props makes the components reusable. Pros are the custom attributes. ```jsx app.component("component-name",{ // configuration object. props:{ prop_name:{ type:DataType, required:true } }, template: // Contains JSX of the component ` <div>{{count}}</div> `, data(){ return { count:0 } }, methods:{ increment(){ this.count+=1; } }, computed:{ isEven(){ return count%2===0 } } }) ``` ```jsx <component-name :prop_name="count"></component-name> ``` **This is Vue 2's syntax, works well with Vue 3 too.** **Vue 3 uses Composition and Options API** ### Communicating Events We can emit events from child component towards the parent component to let the parent component know that it was triggered. In React, we do this by passing the callback reference as a prop to child component. ```javascript // In child Component // this.$emit("event-name",payload) addToCart(){ this.$emit("add-to-cart",this.items[this.selectedItem].id); } ``` ```jsx <parent-component @add-to-cart="updateCart()"></parent-component> // this will trigger the updateCart method inside Main Component ``` ### Two Way Data Binding When working with forms, we cant use `v-bind` because of onoe-way bidning. Instead, we use `v-model` that performs 2-way data binding between data and template. **Two way Data Binding** means the value can be changed from data to template as well as from template to data. This is useful for user inputs. ```jsx <input v-model="name"/> ``` ### Modifiers Modifiers are special postfixes denoted by a dot, which indicate that a directive should be bound in some special way. For example, the .prevent modifier tells the v-on directive to call event.preventDefault() on the triggered event: ```jsx <form @submit.prevent="onSubmit">...</form> ``` ### Slots *Vue's substitute to React's children prop* Using slots, we can inject templates in the component, like children prop in React. We can have multiple named slots in our Component, each one injecting code at differne tlocations in our component. ```jsx // Child Component <template> <h1>Welcome</h1> <slot></slot> </template> ``` To add the content to child, wrap it in `<template>`. ```jsx // Parent Component <Child> <!-- This part will be injected into the slot --> <template> <p>Hello World</p> </template> </Child> ``` We can also inject multiple slots, at different locations in The Child component. For that, we use Named Slots. ```jsx // Child Component <template> <slot name="pre-heading"></slot> <h1>Welcome</h1> <slot name="sub-title"></slot> </template> ``` For passing named slots to the child, `v-slot:slot-name` directive is used ```jsx // Parent Component <Child> <!-- This part will be injected into the slot --> <template v-slot:pre-heading> <h3>This will go to pre-heading</h3> </template> <p>Hello World</p> <template v-slot:sub-title> <h4>This will go to sub-title slot.</h4> </template> </Child> ``` ### Teleport Component Teleport component is one of the new features added in Vue 3. Works just like Portals in React, it is used for adding a component to DOM outside of our Vue app. For teleporting a component, wrap it with `teleport` and refer the DOM node where you want to inject it in `to` attribute. `to` works like `querySelector` method, where we have to pass a CSS selector in it as a parameter. ```jsx <teleport to=".modal"> <Modal/> </teleport> ``` --- ## Composition API with Single-File Components ```jsx <script setup> import vue from "vue" import Child from "./components/Child" </script> <template> <Child/> </template> <style scoped> .class{ style:value; } </style> ``` ### Declaring State using Reactivity API. We can declare state in `<script>` using `ref` and `reactive`. See the difference [here](https://vuejsdevelopers.com/2022/06/01/ref-vs-reactive/). ```jsx import {ref, reactive } from "vue"; const counter1=reacive({count:0}); const counter2=ref(0); ``` To access the state declared using `ref` in the `script`, we have to access its `.value` property. In `template`, we can access it directly ```jsx <script setup> import {ref} from "vue"; const title=ref("Hello World"); </script> <template> <p>{{title}}</p> </template> ``` ### Computed Properties in Composition API In composition API, `computed()` is used to declare a Computed Property. ```jsx <script setup> import {computed} from "vue"; const inStock=computed(()=>{ return this.quantity>0 }) </script> ``` ### Emitting Events in Composition API In Composition API, we have to define the events declared by the component, using `defineEmits([])`. The object is then used to call those custom events. ```jsx <script setup> const emit=defineEmits(["event-name"]); // Emiting the event emit("event-name",payload); </script> ``` ### [Vue Component Lifecycle Hooks in Composition API](https://vuejs.org/api/composition-api-lifecycle.html) --- ## Global State Management in Vue using Vuex Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. ### State Vuex uses a single state tree - that is, this single object contains all your application level state and serves as the "single source of truth." This also means usually you will have only one store for each application. A single state tree makes it straightforward to locate a specific piece of state, and allows us to easily take snapshots of the current app state for debugging purposes. Since Vuex stores are reactive, the simplest way to "retrieve" state from it is simply returning some store state from within a computed property. ```jsx <script setup> import {computed} from "vue"; import {useStore} from "vuex"; const store=useStore(); const counter=computed(()=>store.state.counter); </script> <template> <div> <p>{{counter}}</p> </div> </template> ``` ### Getters Sometimes we may need to compute derived state (computed properties) based on store state, for example filtering through a list of items and counting them. Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. ```javascript const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos (state) { // Getters wil recieve state as thier 1st argument return state.todos.filter(todo => todo.done) } } }) ``` The getters will be available on `store.getters` object and you access values as properties: ```javascript store.getters.doneTodos ``` Getters will also receive other getters as the 2nd argument: ```javascript doneTodosCount (state, getters) { return getters.doneTodos.length } ``` ```javascript store.getters.doneTodosCount; ``` Arguments can also be passed to the getters by returning a function. ```javascript getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } ``` ```javascript store.getters.getTodoById(2) ``` ### Mutations The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the state as the first argument: ```javascript const store = createStore({ state: { count: 1 }, mutations: { increment (state) { // mutate state state.count++ } } }) ``` You cannot call a directly mutation handler. ```javascript store.commit("increment"); ``` You can pass an additional argument to store.commit, which is called the payload for the mutation: ```javascript mutations: { increment (state, payload) { state.count += payload.amount } } ``` ```jsx store.commit('increment', { amount:10 }) ``` An alternative way to commit a mutation is by directly passing an object that has a `type` property: ```jsx store.commit({ type:"increment", amount:10 }) ``` **Mutations are used for synchronous transactions.** ### Actions Actions are similar to mutations, The difference is: - Instead of mutating the state, actions commit mutations. - Actions can contain arbitrary asynchronous operations. ```javascript const store = createStore({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment async (context) { // context.commit('increment') setTimeout(() => { context.commit('increment') }, 1000) } } }) ``` Actions support the same payload format and object-style dispatch: ```javascript // dispatch with a payload store.dispatch('incrementAsync', { amount: 10 }).then(()=>{}).catch(()=>{}) // dispatch with an object store.dispatch({ type: 'incrementAsync', amount: 10 }).then(()=>{}).catch(()=>{}) ``` ## Global State Management in Vue using Pinia Pinia is a global state management solution, an alternative to Vuex, for the Vue. When using pinia for global state management, we define a store or multiple stores. The stores contain: 1. the global state, 2. actions to perform on those states, 3. getters for those states. **Global State** is the state objec in which we store the state that should be accessible by all of the components. *In Vuex, there is only one global state, but in Pinia we can split our state into multiple stores*. **Actions** are the methods that mutate the global state. **Getters** are the methods that work like computed properties and return the transformed state. --- 1. Add `createPinia` middleware to `app` in `main.js` ```javascript import { createPinia } from "pinia"; createApp(App).use(createPinia()).mount("#app"); ``` 2. Create a Pinia Store in `./src/stores` ```javascript // In TaskStore.js import { defineStore } from "pinia"; // defineStore("unique_store_key",options:{}) export const useTaskStore = defineStore("taskStore", { //state method returns and object that represents the initial value of global state. state: () => ({ tasks: [], name: "Pinia Tasks", isLoading: false, }), // getters object contains the getter methods that returns the transformed state getters: { favs() { return this.tasks.filter(task => task.isFav); }, favCount() { return this.tasks.reduce((p, c) => { return c.isFav ? p + 1 : p; }, 0); }, totalCount() { return this.tasks.length; }, }, // actions object contains methods that mutate the global state, the actions can be synchronous or asynchronous without any additional configurations. actions: { async getTasks() { this.isLoading = true; const res = await fetch("http://localhost:3000/tasks"); const data = await res.json(); this.tasks = data; this.isLoading = false; }, addTask(task) { this.tasks.push(task); }, deleteTask(id) { this.tasks = this.tasks.filter(task => task.id !== id); }, toggleFav(id) { const task = this.tasks.find(t => t.id === id); task.isFav = !task.isFav; }, }, }); ``` 3. Access the global state by importing the useTaskStore method, that should return the store. ```jsx <script setup> import {useTaskStore} from "../stores/TaskStore" const taskStore=useTaskStore(); onMounted(()=>{ taskStore.getTasks(); }) <script> <template> <p v-if="taskStore.isLoading">Loading</p> <p v-if="!taskStore.isLoading">Total Tasks: {{taskStore.totalCount}}</p> <!-- totalCount & favCount are getters --> <p v-if="!taskStore.isLoading">Fav Tasks: {{taskStore.favCount}}</p> </template> ``` ### Reset State to initial value For resetting state to its initial value, use `$reset`. ```jsx <button @click="taskStore.$reset">Reset</button> ``` ### Create Refs from the state and getters using storeToRefs `storeToRefs` is a hook that takes all the state and getters from the store and creates their refs. ```jsx <script setup> import {storeToRefs} from "pinia"; import {useTaskStore} from "../stores/TaskStore"; const taskStore=useTaskStore(); const {tasks, isLoading, totalCount, favCount, favs} = storeToRefs(taskStore); </script> <template> <p>{{totalCount}}</p> </template> ``` <!-- ## [Vue Router](https://vueschool.io/lessons/introduction-to-vue-router-4) --> --- ## TODO - Add Frontend Basic Concepts Section. - Add composables. - Add Vue-Router Section. - Add Old Composition API Section. - Add Vue-Query Section. --- ## CHANGELOG - Added References. - Added Vuex Section. - Added Pinia Section. - Added Frontend Basic Concepts Section (In Progress).