Enhance the functionality of VTextField in Vuetify

Exploring the idea of extending a vuetify VTextField component to create a reusable password-field, I encountered warnings against prop mutation in Vuejs, deeming it an "anti-pattern".

One approach I tried involved using a computed property to override the prop, which did work but resulted in conflict warnings in the web console.

Below is a basic example:

import Vue from 'vue'
import { VTextField } from 'vuetify/lib'

export default Vue.extend({
    name: 'password-field',
    mixins: [VTextField],
    data: () => ({
        reveal: false
    }),
    computed: {
        function type () {
            return this.reveal ? 'text' : 'password'
        }
    }
})

It seems like there should be a way to utilize mixins to extend VTextField and selectively replace props with computed properties. The ultimate goal is to have a reactive value under the control of the password-field component rather than the parent.

Could my current approach be incorrect?

UPDATED

After receiving valuable input from Yom S, I successfully created a custom extension of VTextField. We opted for suggestion #2, an SFC templated component.

For others who may come across this topic, here is an implementation compatible with Typescript:

<!-- put this in components/password-field.vue -->
<template>
  <v-text-field
    v-bind="computedProps"
    v-on:click:append="reveal = !reveal"
    v-on="listeners$"
  ></v-text-field>
</template>

<script lang="ts">
import Vue from 'vue'
import { VTextField } from 'vuetify/lib'

export default {
  name: 'PasswordField',
  extends: VTextField,

  props: {
    label: {
      type: String,
      default: 'Password'
    },
    rules: {
      type: Array,
      default: () => [(v: string) => {
        return /((?=.*\d)(?=.*[a-z])(?=.*[!@#$%^&*()?.]).{8,})/i.test(v) ||
          'At least 8 char; upper and lowercase, a number and a special char'
      }]
    }
  },

  data: () => ({
    reveal: false
  }),

  computed: {
    computedProps: function () {
      return {
        ...this.$props,
        type: this.reveal ? 'text' : 'password',
        appendIcon: this.reveal ? 'mdi-eye' : 'mdi-eye-off'
      }
    }
  }

} as Vue.ComponentOptions<Vue>
</script>

Here's a simple example demonstrating how to use this component:

<template>
  <v-form v-model="formValid">
    <password-field v-model="newPassword/>
    <v-btn :disabled="!formValid">Change</v-btn>
  </v-form>
</template>

<script lang="ts">
import Vue from 'vue'
import PasswordField from '@/components/password-field.vue'

export default Vue.extend({
  name: 'ChangePasswordForm',
  data: () => ({
    formValid: false,
    newPassword: ''
  })
})
</script>

Answer №1

It would have been beneficial if the specific type property could be made sync-able; however, since it is not, a workaround can be implemented by essentially re-rendering the VTextField component while also extending its functionality.

I must note that this solution may not be perfect as it does have some limitations which make it an imperfect wrapper. Nevertheless, it does serve the purpose outlined in your query.

Main drawbacks include:

  • Scoped slots (e.g., append, append-outer) will not output the expected elements.

For this specific use case, let's refer to this enhanced component as "PasswordField," and its usage would resemble:

<PasswordField 
  label="Enter your password"
  :revealed="revealed" 
  append-outer-icon="mdi-eye" 
  @click:append-outer="togglePassword" />

The inclusion of the append-outer-icon and the icon-toggle feature should ideally be encapsulated within the component itself.

Below represents the implementation:

PasswordField.js

  • Advantages:
    • Relatively simpler without the need for complex templating.
    • Shorter compilation times compared to Vue template-based components due to being just a JavaScript file without going through extensive Vue template compiling.
  • Disadvantages:
    • Seemingly ineffective event listeners.
import { VTextField } from 'vuetify/lib';

export default {
  name: 'PasswordField',
  extends: VTextField,

  props: {
    revealed: {
      type: Boolean,
      default: false
    }
  },

  render(h) {
    const { revealed, ...innerProps } = this.$options.propsData;

    return h(VTextField, {
      listeners: this.$listeners,

      props: {
        ...innerProps,
        type: revealed ? 'text' : 'password'
      }
    })
  } 
}

Note the utilization of extends from the base component (VTextField) where the original render function is customized resulting in a personalized virtual node or VNode.

However, there are noted shortcomings such as the failure to properly listen to emitted events. A feasible solution to address this would be highly appreciated.

As a final recourse, resorting back to incorporating a template structure along with computed properties is recommended for ensuring proper binding and execution, quite literally focusing only on the properties to bind, excluding extraneous data.

PasswordField.vue

  • Advantages:
    • Enhanced reliability in terms of functionality.
    • Event listeners operate seamlessly as intended.
    • Single File Components (SFC) excel in performance using this method.
  • Disadvantages:
    • Moderately repetitive as manual management of prop bindings and event registration is required.
    • Slightly slower compilation time (though typically insignificant).
<template>
  <v-text-field
    v-bind="computedProps"
    v-on="$listeners">
  </v-text-field>
</template>

<script>
  import { VTextField } from 'vuetify/lib';

  export default {
    name: 'PasswordField',
    extends: VTextField,

    props: {
      revealed: {
        type: Boolean,
        default: false
      }
    },

    computed: {
      computedProps() {
        return {
          ...this.$props,
          type: this.revealed ? 'text' : 'password'
        }
      }
    }
  }
</script>

This approach hopefully proves helpful in some way!

Answer №2

Custom Code Snippet

<template>
  <v-text-field v-bind="$props" v-on="$listeners" class="custom-class">
    <template
      v-for="slot in Object.keys($scopedSlots)"
      :slot="slot"
      slot-scope="scope"
    >
      <slot :name="slot" v-bind="scope" />
    </template>
  </v-text-field>
</template>

<script>
import { VTextField } from 'vuetify/lib'

export default {
  extends: VTextField,

  props: {
    filled: { default: true },
  },
}
</script>


Insight

Enhancing a Vuetify Component

Here, we are focusing on extending the functionality of a v-text-field component.

  • In your script section, import the VTextField component:
import { VBtn } from 'vuetify/lib'
  • Extend your component from VTextField:
export default {
  extends: VBtn,
}

Customizing the Template

  • v-bind="$props" binds all available props
  • v-on="$listeners" binds all events
  • We iterate over $scopedSlots to redefine scoped slots

Sample template customization:

<template>
  <v-text-field v-bind="$props" v-on="$listeners">
    <template
      v-for="slot in Object.keys($scopedSlots)"
      :slot="slot"
      slot-scope="scope"
    >
      <slot :name="slot" v-bind="scope" />
    </template>
  </v-text-field>
</template>

You now have the flexibility to add custom classes, alter templates, or make other modifications.

Overriding Default Props

  • Redefine your props within the props object and assign default values if needed

For instance, setting the text field to be filled by default:

props: {
    filled: { default: true },
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering an issue of Property not existing on JSX intrinsic elements when utilizing TSS with Javascript (without TypeScript)

I am currently working on a JavaScript project (using create-react-app 2.0) and utilizing tsserver without Typescript. I encountered a linting error that states: Property 'svg-icon' does not exist on type 'JSX.intrinsictElements'. Thi ...

Tips for creating an HTTP only cookie in NestJS

Currently, I am in the process of incorporating JWT authorization with both an accessToken and refreshToken. The requirement is to store these tokens in HTTP-only cookies. Despite attempting this code snippet, I have encountered an issue where the cookies ...

What is the best way to retrieve the value of the "Observer" object?

I am a user of Vue.js and I have the following data in this.suspendedReserveMemo: this.suspendedReserveMemo [__ob__: Observer]650: "memo3"651: "memo4"652: ""653: ""654: ""655: ""656: ""657: ""658: ""659: ""660:""661: ""662: ""length: 663__ob__: Observer {v ...

Error 404: Page not found on nginx server version 1.18.0

An issue occurred when attempting to reload the 404 Not Found page nginx / 1.18.0 If you input the # symbol into the link For example: , the transition will occur / How can this be resolved? server { listen 80; server_name localhost; ...

Having trouble locating the module '.outputserver ode_modulespiniadistpinia' in Nuxt3 and Pinia?

I recently integrated Pinia into my Nuxt3 project, and while everything works fine in development mode, I encountered an error when trying to access the application in production mode resulting in the website freezing. [h3] [unhandled] H3Error: Cannot find ...

Tips for maintaining retrieved data when parameters are updated on the preceding page in Nuxt framework

Is there a way to maintain the data received from "/about/1" when transitioning to "/about/2" without remounting the component? Currently, when the route parameter changes using [/about/:page]this.$route.params.page, the component is remounted causing the ...

Trouble arises when attempting to import React JSX project/modules from npm into an AngularJS TypeScript module

In the process of developing a proof-of-concept React framework/library, I aim to create a versatile solution that can be utilized in both React and AngularJS applications. To achieve this goal, I have initiated two separate projects: - sample-react-frame ...

Guide to developing a universal store extension

I've been attempting to create a reactive global $store object using a plugin, but so far I have not been successful in getting it to function as intended. store.ts: import {reactive} from "vue"; export default { install: (app:any, opt ...

Retrieving a date and time value from an object based on a specific range

Having trouble displaying all the user IDs and creation dates within a specific date range in a table. I'm struggling with filtering the data based on dates. Can someone help me identify what I might be doing wrong? I've attempted to replicate th ...

Set up Vue to have both a development and a production build

Recently, I took over a Vue project that exclusively creates production builds and applies uglification to everything. Unlike my previous projects, this one doesn't utilize a webpack.config.js file; instead, it relies on a vue.config.js configuration ...

What is the best way to convert a tuple containing key/value pairs into an object?

How can the function keyValueArrayToObject be rewritten in order to ensure that the type of keyValueObject is specifically {a: number; b: string}, instead of the current type which is {[k: string]: any}? const arrayOfKeyValue = [ {key: 'a', val ...

Angular Authentication Functionality

I need to create a loggedIn method in the AuthService. This method should return a boolean indicating the user's status. It will be used for the CanActivate method. Here is a snippet of code from the AuthService: login(email: string, password: string) ...

Formatting a phone number using v-model in VueJs

Is there a way to automatically format phone numbers entered into the input field as (123) - 456 - 78 - 90? <template> <div v-for="about in abouts"> <input type="text" v-model="about.phone"> <input t ...

Streamlined [JavaScript?] solution for displaying and modifying several location-specific purchasing and selling rates

No, this is not related to interviews or cryptocurrencies! :) It is for a non-profit personal web application designed to enhance a game. This question involves both finance and coding. I am developing this web app using Vue.js, so I prefer a JavaScri ...

The SunEditor onChange event does not reflect updated state information

software version next: 12.0.7 suneditor: 2.41.3 suneditor-react: 3.3.1 const SunEditor = dynamic(() => import("suneditor-react"), { ssr: false, }); import "suneditor/dist/css/suneditor.min.css"; // Import Sun Editor's CSS Fi ...

Troubleshooting type conflicts while utilizing the 'withRouter' function in Typescript

Currently, I am delving deeper into React and TypeScript, seeking to expand my understanding and practical experience. While utilizing withRouter from react-router-dom, I encountered a typing error. The issue arose within my simplistic code snippet. I att ...

Cross-component communication in Angular

I'm currently developing a web-based application using angular version 6. Within my application, there is a component that contains another component as its child. In the parent component, there is a specific function that I would like to invoke when ...

What causes the difference in behavior between using setInterval() with a named function as an argument versus using an anonymous function?

I can't seem to figure out why using the function name in setInterval is causing issues, while passing an anonymous function works perfectly fine. In the example that's not working (it's logging NaN to the console and before the first call, ...

Transforming a jQuery menu into an active selection with Vue JS

I am looking to transition away from using jQuery and instead utilize Vue for the front end of a menu. Specifically, I need to add an active class and a 'menu-open' state to the appropriate nested list items, similar to what is achieved in the pr ...

Unable to assign a value to a constant within the class constructor

I'm aware that in TypeScript, readonly properties can only be assigned a value within a class constructor. However, I encountered an error while trying to do so inside the constructor of my class, specifically related to an array method handler. class ...