Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind vuex state and mutations to checkbox component properties in TypeScript-based Vue

Problem

Create the checkbox as Vue component, herewith:

  1. No logic inside checkbox component allowed: all event handlers and also checked property are fully depends on external logic, which could be the vuex store.
  2. We should not watch the checkbox "checked" state: checked it or not, it depends on, again, external logic, e. g. vuex state or getter.

Try 1

Concept

The checkbox component has checked and onClick properties, which value are off course, could be dynamic.

Component

Template in Pug language:

label.SvgCheckbox-LabelAsWrapper(:class="rootElementCssClass" @click.prevent="onClick")
  input.SvgCheckbox-InvisibleAuthenticCheckbox(
    type="checkbox"
    :checked="checked"
    :disabled="disabled"
  )
  svg(viewbox='0 0 24 24').SvgCheckbox-SvgCanvas
    path(
      v-if="!checked"
      d='M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z'
    ).SvgCheckbox-SvgPath.SvgCheckbox-SvgPath__Unchecked
    path(
      v-else
      d='M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z'
    ).SvgCheckbox-SvgPath.SvgCheckbox-SvgPath__Checked
  span(v-if="text").SvgCheckbox-AppendedText {{ text }}
import { Vue, Component, Prop } from 'vue-property-decorator';

@Component
export default class SimpleCheckbox extends Vue {

  @Prop({ type: Boolean, required: true }) private readonly checked!: boolean;

  @Prop({ type: Boolean, default: false }) private readonly disabled!: boolean;

  @Prop({ type: String }) private readonly text?: string;
  @Prop({ type: String }) private readonly parentElementCssClass?: string;

  @Prop({ type: Function, default: () => {} }) private readonly onClick!: () => void;
}

Store module

import { VuexModule, Module, Mutation } from "vuex-module-decorators";
import store, { StoreModuleNames } from "@Store/Store";


@Module({ name: StoreModuleNames.example, store, dynamic: true, namespaced: true })
export default class ExampleStoreModule extends VuexModule {

  private _doNotPreProcessMarkupEntryPointsFlag: boolean = true;

  public get doNotPreProcessMarkupEntryPointsFlag(): boolean {
    return this._doNotPreProcessMarkupEntryPointsFlag;
  }

  @Mutation
  public toggleDoNotPreProcessMarkupEntryPointsFlag(): void {
    this._doNotPreProcessMarkupEntryPointsFlag = !this._doNotPreProcessMarkupEntryPointsFlag;
  }
}

Usage

SimpleCheckbox(
  :checked="relatedStoreModule.doNotPreProcessMarkupEntryPointsFlag"
  :onClick="relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag"
  parentElementCssClass="RegularCheckbox"
)
import { Component, Vue } from "vue-property-decorator";
import { getModule } from "vuex-module-decorators";
import ExampleStoreModule from "@Store/modules/ExampleStoreModule";
import template from "@Templates/ExampleTemplate.pug";
import SimpleCheckbox from "@Components/Checkboxes/MaterialDesign/SimpleCheckbox.vue";

@Component({ components: { SimpleCheckbox } })
export default class MarkupPreProcessingSettings extends Vue {
  private readonly relatedStoreModule: ExampleStoreModule = getModule(ExampleStoreModule);
}

Warings

Appears if click the checkbox. Checkbox works as we need, but some of Vue concept has been violated.

enter image description here

vue.common.dev.js:630 [Vue warn]: $attrs is readonly.

found in

---> <SimpleCheckbox> at hikari-frontend/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue
       <MarkupPreProcessingSettings>
         <Application> at ProjectInitializer/ElectronRendererProcess/RootComponent.vue
           <Root>

vue.common.dev.js:630 [Vue warn]: $listeners is readonly.

found in

---> <SimpleCheckbox> at hikari-frontend/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue
       <MarkupPreProcessingSettings>
         <Application> at ProjectInitializer/ElectronRendererProcess/RootComponent.vue
           <Root>

vue.common.dev.js:630 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "checked"

found in

---> <SimpleCheckbox> at hikari-frontend/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue
       <MarkupPreProcessingSettings>
         <Application> at ProjectInitializer/ElectronRendererProcess/RootComponent.vue
           <Root>

Musings

This warning are being emitted frequent cause is new value to some vue-property has been assign inside component. Explicitly, I did not the manipulations like this.

The problem is in :onClick="relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag". It looks like it compiles to something like <component>.$props.onClick="<vuex store manipulations ...>" - if it so, it is implicit property mutation inside component.

Try 2

Concept

Based Vue documentation, Customizing Component section:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

The equivalent for TypeScript with vue-property-decorator will be:

import { Vue, Component, Model } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Model('change', { type: Boolean }) readonly checked!: boolean
}

Component

label.SvgCheckbox-LabelAsWrapper(:class="rootElementCssClass")
  input.SvgCheckbox-InvisibleAuthenticCheckbox(
    type="checkbox"
    :checked="checked"
    :disabled="disabled"
    @change="$emit('change', $event.target.checked)"
  )
  svg(viewbox='0 0 24 24').SvgCheckbox-SvgCanvas
    // ...
import { Vue, Component, Prop, Model } from "vue-property-decorator";

@Component
export default class SimpleCheckbox extends Vue {

  @Model('change', { type: Boolean }) readonly checked!: boolean;

  @Prop({ type: Boolean, default: false }) private readonly disabled!: boolean;

  @Prop({ type: String }) private readonly text?: string;
  @Prop({ type: String }) private readonly rootElementCssClass?: string;
}

Usage

SimpleCheckbox(
  v-model="doNotPreProcessMarkupEntryPointsFlag"
  rootElementCssClass="RegularCheckbox"
)

In TypeScript, to use the v-model, we need to declare getter and same-name setter:

@Component({
  template,
  components: {
    SimpleCheckbox,
    // ...
  }
})
export default class MarkupPreProcessingSettings extends Vue {

  private readonly relatedStoreModule: MarkupPreProcessingSettingsStoreModule =
      getModule(MarkupPreProcessingSettingsStoreModule);
  //...
  private get doNotPreProcessMarkupEntryPointsFlag(): boolean {
    return this.relatedStoreModule.doNotPreProcessMarkupEntryPointsFlag;
  }

  private set doNotPreProcessMarkupEntryPointsFlag(_newValue: boolean) {
    this.relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag();
  }
}

Warnings

Same errors set:

enter image description here

Limitations

First, we need to create new getter and setter in Vue Component class. It will be cool if possible to avoid id. Unfortunately, for vuex class (by vuex-module-decorators), TypeScript setters are not available, we need use the @Mutation-decorated method instead.

Also, this solution will not work for elements rendered by v-for. It make this solution useless.

Try 3

Concept

Event emitter and custom event listener usage. This solution also works properly, but Vue emits warnings.

Component

label.SvgCheckbox-LabelAsWrapper(:class="rootElementCssClass" @click.prevent="$emit('toggled')")
  // ...

Usage

SimpleCheckbox(
  :checked="relatedStoreModule.doNotPreProcessMarkupEntryPointsFlag"
  @toggled="relatedStoreModule.toggleDoNotPreProcessMarkupEntryPointsFlag"
  rootElementCssClass="RegularCheckbox"
)

Warnings

enter image description here


Update

There are some riddles left yet, however problem has been solved. See my answer below.

like image 706
Takeshi Tokugawa YD Avatar asked Sep 26 '19 06:09

Takeshi Tokugawa YD


1 Answers

This warnings occurred in Electron application. The SimpleCheckbox is from node_modules, however this library is still in development, so it has been provided by npm link.

When I tried to make reproduction, I created the SPA for browser and place SimpleCheckbox to same project (did not get from node_modules). The first solution works! (I don't care about second and third ones - I need refined from peel elegant solutions only).

I suggested that cause is npm link, publish my libraries and installed it via npm install. The warnings has disappeared!

Conclusion

It was not the first time when npm link causes the problem like this. Here is another case.

I still does not understand this this case in depth - I just published some experimental data. The "So, what if library is in development yet?" question still has not answer. I tried Lerna - at the first time warnings has disappeared, but when I move my project to Lerna, the warnings appears again - the regularity is unclear yet for me.

like image 54
Takeshi Tokugawa YD Avatar answered Nov 03 '22 18:11

Takeshi Tokugawa YD