Tips on preventing an overload of events in child components in Vue

I have a Object instance that looks like this:

class Engine {
    id = 0;
    crankRPM: = 200;
    maxRPM = 2400;
    numCylinders = 8;
    maxOilTemp = 125;
    isRunning = false;

    start() { ... }
    stop() { ... }
}

Now, I need to create an engine component.

Vue strongly discourages mutating the state of a property in any child component so now I need to follow this approach:

Here is the engine-ui Component definition:

<template>
    <card> // Visual styling omitted
        {{ engine.id }}
        <input :value="engine.crankRPM" @input="$emit('changeCrankRPM', $event.target.value)"></input>
        <input :value="engine.maxRPM" @input="$emit('changeMaxRPM', $event.target.value)"></input>
        <input :value="engine.numCylinders" @input="$emit('changeMumCylinders', $event.target.value)"></input>
        <input :value="engine.maxOilTemp" @input="$emit('changeMaxOilTemp', $event.target.value)"></input>
        <toggle type="toggle" :value="engine.isRunning" @input="$emit('changeIsRunning', $event.target.value)  </toggle>
    </card>
</template>
<script lang="ts">
import { Engine } from "src/code/Engine";
import { defineComponent } from "vue";

export default defineComponent({
    name: "engine-ui",
    props: {
        engine: {
            type: Engine,
            required: true,
        },
    },
});
</script>

Usage in parent component:

<template>
    ...
    <engine-ui :engine="myEngine" 
        @changeCrankRPM="myEngine.crankRPM = $event.target.value"
        @changeMAxRPM="myEngine.maxRPM = $event.target.value"
        @changeNumCylinders="myEngine.numCylinders = $event.target.value"
        @changeOilTemp="myEngine.maxOilTemp = $event.target.value"
        @changeIsRunning="myEngine.isRunning = $event.target.value"/>
    ...
</template>

This method leads to verbose and clunky code. If there are many fields in the Engine class, it becomes cumbersome to manage in every parent component using engine-ui as it creates a large block of text.

What is the most efficient way to design this interaction?

If modifications are made to the Engine class, updates must be made to the engine-ui component and every instance in any parent component since emits are processed as strings.

Answer №1

In this straightforward example, all you have to do is specify the field types. The data flows through the update function, where it's crucial to properly cast the value to the correct type since default @input or @change events always present the value as a string.

This approach effectively eliminates the need for individual update functions for each property, reducing verbosity.

If necessary, this setup can be effortlessly extended to accommodate rendering custom components for specific input types.

const { defineComponent, createApp, toRefs, reactive } = Vue;

class Engine {
  id = 0;
  crankRPM = 200;
  description = 'Some description';
  maxRPM = 2400;
  numCylinders = 8;
  maxOilTemp = 125;
  isRunning = false;
}

const app = createApp({
  setup() {
    const state = reactive({
      engine: new Engine(),
      engineFields: [
        { key: 'crankRPM', type: 'number' },
        { key: 'maxRPM', type: 'number' },
        { key: 'description', type: 'text' },
        { key: 'numCylinders', type: 'number' },
        { key: 'maxOilTemp', type: 'number' },
        { key: 'isRunning', type: 'boolean' }
      ]
    })
    const update = ({ value, field }) => {
      state.engine[field.key] = field.type === 'number' ? +value : value;
    };
    return {
      ...toRefs(state),
      update
    }
  }
}).mount('#app')
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>
<div id="app">
    <div class="card">
        {{ engine.id }}
        <template v-for="field in engineFields" :key="field.key">
          <label v-if="field.type === 'boolean'">
            <input type="checkbox"
                   :checked="engine[field.key]"
                   @change="update({ value: $event.target.checked, field })">
            {{ field.key }}
          </label>
          <input v-else
                 :type="field.type"
                 :value="engine[field.key]"
                 @input="update({ value: $event.target.value, field })">
          
          <!-- you can have as many input types as you want,
               just replace v-else above with v-else-if and add
               another case (e.g: textarea, custom components, ...) -->
        </template>
    </div>
    <pre v-text="engine" />
</div>

Answer №2

To ensure code maintainability, it is recommended to minimize template logic.

The conventional approach is to consider engine as a singular entity and implement two-way binding.

Using v-model, the implementation would look like this:

<engine-ui v-model="myEngine"/>

and

...
<div v-for="(_, field) in modelValue">
  <input v-model="modelValue[field]"/>
...
props: {
    modelValue: Engine
},

Modifying a prop directly is not generally advised as it complicates data flow. One way to handle this is by emitting a cloned instance of Engine from a child component whenever a field changes, but this method can be inefficient. Alternatively, updates can be managed within the parent component as follows:

...
<engine-ui :value="myEngine" @update="onEngineUpdate" />
...
methods: {
    onEngineUpdate({ field, fieldValue }) {
         this.myEngine[field] = fieldValue;
    },
},
...

and

...
<div v-for="(fieldValue, field) in value">
  <input :value="fieldValue" @input="$emit('update', { field, fieldValue : $event.target.value })" />
...
props: {
    value: Engine
},
...

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

Vue (Gridsome) App encountering 'Cannot POST /' error due to Netlify Forms blocking redirect functionality

Currently, I am in the process of developing my personal website using Gridsome. My goal is to incorporate a newsletter signup form through Netlify Forms without redirecting the user upon clicking 'Submit'. To achieve this, I utilize @submit.prev ...

Using VueJS to dynamically load a separate component into a Vue instance

Currently, I am working on revamping our web platform at my job. This includes migrating a significant amount of outdated JavaScript/jQuery code to VueJS. We have a "global.js" file that contains our Vue components and a "vendor.js" file that includes Vue ...

I attempted to retrieve the information using the v-for loop, but I ran into a roadblock when trying to access the data within the generalRights section. Can anyone pinpoint where I may have gone wrong?

<div id="ags"> <div class="cardgs" v-for="(informations, detailedinformations) in subdetailedinformations" :key="detailedinformations" > <b-card ...

Guide to incorporating external code in InversifyJS without direct control

I'm wondering if it's feasible to add classes that are not editable. Inversify seems to rely heavily on annotations and decorators, but I'm curious if there is an alternative method. ...

Accessed a property that is not defined on the instance during rendering

One of the components I'm working on displays data that is defined in the component's state. To access this data, I created a getter: export default createStore({ state: { foo: true, }, getters: { getFoo: state => state.fo ...

Angular 2 - retrieve the most recent 5 entries from the database

Is there a way to retrieve the last 5 records from a database? logs.component.html <table class="table table-striped table-bordered"> <thead> <tr> <th>Date</th> <th>Logging ...

Type errors in NextJS are not being displayed when running `npm run dev`

When encountering a typescript error, I notice that it is visible in my editor, but not in the browser or the terminal running npm run dev. However, the error does show up when I run npm run build. Is there a method to display type errors during npm run d ...

Can SystemJS, JetBrains IntelliJ, and modules be combined effectively?

Looking for some clarification on the functionality of module includes and systemJS within an Angular2 app structure. I have set up a basic Angular2 app with the following layout: -app |-lib (contains shims and node libraries) |-components |-app |-app. ...

My router-outlet is malfunctioning when trying to display my component

I am currently diving into learning Angular 2 in order to revamp my personal website. However, I've encountered an issue where my application fails to load the component when I navigate to the appropriate route by clicking on the navigation bar. Insi ...

Oops! Issue encountered while trying to read the file "src/core/database/config.ts"

Need help with migrating a database in a Node Nest.JS application. When running the npx sequelize-cli db:migrate shell command, I encountered the following exception: Error details: Error: TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".t ...

Is there a way to enable intellisense in vscode while editing custom CSS within a material-ui component?

Is there a vscode extension recommendation for intellisense to suggest css-in-js for customized material ui components in .tsx files? For example, I want intellisense to suggest 'backgroundColor' when typing. The closest I found is the 'CSS- ...

Typescript: Exploring the Assignability of Numbers, Strings, and More to Null

Why does TypeScript display errors only when assigning a string to a number, but not when assigning null to a number? export type ArrayWithNumberOrString = Array<number | string>; export type ArrayWithNumberOrNull = Array<number | null>; f ...

Creating a string array within an array in TypeScript

Struggling to return a type-safe Typescript v3.5 array without having to declare it within the method body? This array should consist of multiple string arrays. Desiring something along the lines of: foo(): Array<Array<string>>: // perfo ...

What is the best way to move an element from a relative to an absolute position smoothly?

I've been working on my portfolio site and came up with a unique idea for the landing page – having the name placed in the center and then transitioning into a navbar when a route is selected. However, I'm facing a dilemma. To achieve this eff ...

Employing a provider within a different provider and reciprocally intertwining their functions

I'm currently facing an issue with two providers, which I have injected through the constructor. Here's the code for my user-data.ts file: @Injectable() export class UserDataProvider { constructor(private apiService: ApiServiceProvider) { ...

utilize images stored locally instead of fetching them from a URL path in a Vue.js project

Hey there fellow Developers who are working on Vuejs! I'm encountering something strange in the app I'm building. I am attempting to modify the path of image requests based on a particular result, which causes the images to change according to th ...

The data set in a setTimeout is not causing the Angular4 view to update as expected

I am currently working on updating a progress bar while importing data. To achieve this, I have implemented a delay of one second for each record during the import process. Although this may not be the most efficient method, it serves its purpose since thi ...

CLI package enables exporting API facilities

I have a mono repository containing a CLI package (packages/cli) and a web application (apps/web). I want to utilize the public API of the CLI within the web app. The CLI package is packaged with tsup: export default defineConfig({ clean: false, dts: ...

The program encountered an unexpected symbol. It was expecting a constructor, method, accessor, or property. Additionally, there is a possibility that the object is 'undefined'

Can someone please help me figure out what's wrong with this code snippet? import cassandra from "cassandra-driver"; class Cass { static _cass : cassandra.Client; this._cass = new cassandra.Client({ contactPoints: ['localhost&ap ...

Troubleshooting React TypeScript in Visual Studio Code

I've recently set up an ASP Core project with the React TypeScript template, but I'm encountering difficulties when it comes to debugging. The transition between the TypeScript code and the corresponding generated JavaScript code is proving to be ...