Validity of Vue 3 Typescript properties checked during compilation and runtime

We are currently developing a Vue 3 component library, and as the project grows in scale, we have refactored it with TypeScript to improve the development experience. However, we are facing a challenge. Our library is being used by teams in different environments, some with TypeScript and bundler setups, and others without. This means that for almost every component, we need to validate prop types both at compile-time and runtime.

props: {
  someProp: {
    type: String as PropType<'enum1' | 'enum2' | 'enum3'>,
    validator: (v: string) => {
      return ['enum1', 'enum2', 'enum3'].includes(v)
    }
  }
}

While the code is functional and beneficial for both TypeScript and JavaScript teams, as it provides code completion and error detection capabilities, we are not satisfied with the duplication of code. Despite <'enum1' | ...> being a type definition and ['enum1', ...] being an array, they essentially contain the same information but are written repeatedly.

Is there a way to streamline the process and define props with PropType and validator without unnecessary repetition?

Answer №1

At the moment, my current approach looks something like this:

export const createProperty = <
  T = any,
  R extends boolean = boolean,
  D extends T = T
>({
  type?: any,
  /* defining allowable values for this property */
  values?: readonly T[],
  required?: R,
  /* a required property cannot have a default value */
  /* a default value cannot be set for a required property */
  defaultValue?: R extends true
    ? never
    : D extends Record<string, unknown> | Array<any>
      ? () => D
      : D,
  /* additional validation rules */
  validator?: (val: any) => boolean
} = {}) => {
  return {
    type: type as PropType<T | never>,
    required: !!required,
    default: defaultValue,
    validator: (val: any) => {
      let valid = false

      // checking if the property value is within the defined values array
      // or matches the default value
      if (values) {
        valid =|| [...values, defaultValue].includes(val)
      }
      if (validator) {
        valid =|| validator(val)
      }

      return valid
    }
  }
}

Consequently,

someProperty: createProperty({
  type: String,
  values: ['enum1', 'enum2', 'enum3']
})

will result in

someProperty: {
  type: String as PropType<'enum1' | 'enum2' | 'enum3'>,
  validator: (val) => {
    return ['enum1', 'enum2', 'enum3'].includes(val)
  }
}

if (props.someProperty === 'illegalValue') {
  // ERR: will always return false as 'enum1' | 'enum2' | 'enum3' is not equal to 'illegalValue'
}

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

What is the significance of including parameter names in Typescript function type signatures?

Just diving into typescript for the first time, so bear with me... I decided to create a simple filter function for a container I had created class Container<T> { filter(predicate: (T) => boolean): Container<T> { for(const elem ...

Typescript - Conditional imports

When working with the moment-timezone module, one issue that arises is receiving a warning if it is included multiple times. In my specific case, I have a module that necessitates the use of this timezone functionality. Since I am unsure whether or not the ...

Using Next.JS useRouter to access a dynamic route will result in receiving an empty object as the return value

I've encountered an issue with dynamic routing in my specialized calendar application built with Next.JS. One of my pages is working perfectly fine while the other is not functioning at all. The first page (working): // pages/date/[year]/[month]/[day ...

TS1316 Error: You can only have global module exports at the top level of the file

Encountering difficulties while trying to compile an older typescript project that I am revisiting. The build process is failing due to an issue with q. I suspect it may be related to the tsc version, but no matter which version I try, errors persist. Som ...

The issue of resolving custom paths imports in Typescript has been a persistent challenge for developers

Currently, I am developing a project in PHP and utilizing Typescript. I aim to follow a monorepo pattern similar to what NX offers. However, I am facing challenges when attempting to compile typescript for each of my apps. Here is the current structure of ...

Beautiful ExpressionChangedAfterItHasBeenCheckedError

I need your help Input field where I can enter a Student email or IAM, which will be added to a string array List displaying all the students I have added, using a for loop as shown below Delete button to remove a student from the array The list has a sp ...

Encountering a 404 Not Found error upon refreshing a page in Vue JS

I'm encountering a "404 Not found" error when I refresh any page on my Vue JS application hosted in Azure Web APP (IIS Server), except the default web page index.html I've followed the rewrite configuration provided in the VueJs official documen ...

TypeScript and Redux mapDispatchToProps are not in sync

Below is my React component written in TypeScript: import React from 'react'; import {connect, ConnectedProps} from 'react-redux'; import logo from './assets/logo.png'; // import { Counter } from './features/counter/Count ...

Here is a unique rewrite: "Strategies for accessing interface-defined fields instead of mongoose schema-defined fields

Is there a way to modify my code to return "id" instead of "_id" and without "__v" to the client using express, mongoose, and TypeScript? Code snippet: Interface: export default interface Domain { name: string; objects: DomainObject[] } Creation Int ...

Tips for streamlining code using switch statements in vue.js

Is there a more efficient way to simplify this switch statement for handling 5 different cases? Can I streamline the process of updating the completion status based on each step? data() { return { stepOneIsCompleted: false, ...

Updating components reactively in Vue.js when a click event occurs

I have a dilemma with my component that allows users to add or remove an item from their favorites. While the functionality works smoothly, there is no visual indication for the user to know whether the item has been added to favorites or not. Currently, I ...

There was an issue with the Vuejs Router that prevented the property '$emit' from being read due to a TypeError because

I am facing an issue with a router configuration like this: { path: 'user', redirect: '/user', name: 'user', component: { render(c) { return c('router-view', { on ...

Converting lengthy timestamp for year extraction in TypeScript

I am facing a challenge with extracting the year from a date of birth value stored as a long in an object retrieved from the backend. I am using Angular 4 (TypeScript) for the frontend and I would like to convert this long value into a Date object in order ...

Tips for specifying a variable as a mandatory key attribute within an array

Is there a way to dynamically determine the type of key attribute in an array? const arr = [ { key: 'a' }, { key: 'b' }, { key: 'c' }, ]; type key = ??? // Possible values for key are 'a', 'b', or &a ...

When a custom icon is clicked in the vue-select dropdown, the custom method is not activated

In my current project, I am creating a vue-component based on vue-select. Within this component, I have added a custom info Icon. The issue I am facing is that when the user clicks on the Icon, instead of triggering my custom method getInfo, it opens the s ...

What is the proper type for an object and an array of strings?

We have an array example below. const sampleArray = [ {names: ['example1', 'example2']}, 'another', 'final' ]; Additionally, here is a type error example. The error message reads as follows: "Type 'string ...

One effective way to utilize await/async within the Vue mounted lifecycle hook is by

I am facing an issue where the mapGetters value is showing null in my computed property because the preferences method is not executed completely. I need to wait until the store has set the getter and setter. I have tried using async/await but it's no ...

The history mode router in Vue disrupts the dynamic URL hashing

When using Vue version 2.6.14 and setting Vue Router to history mode, encountering a URL with a hashtag "#" can cause issues with dynamic paths. const router = new VueRouter({ base: `${process.env.VUE_APP_PUBLIC_PATH}`, mode: 'history', rou ...

Include a scrollbar within a Bootstrap table grid under a div with a width of 12 columns

How can I add a scrollbar to the grid below? It is using a bootstrap table inside a bootstrap col 12 div. I have attempted to use the following CSS, but it does not apply a scrollbar, it only disrupts the columns. divgrid.horizontal { width: 100%; ...

Update the field 'payments._id' in MongoDB to a timestamp

I need to change the value of 'payments._id' to a timestamp. In MongoDB, the 'payments._id' object is automatically generated when inserting a document into the collection. onMounted(async () => { const res = await axios.get(" ...