Search code examples
typescriptvue.jsvuejs2vue-class-components

How to simplify and reset props from parent to child components?


I am trying to reset data passed as props to children components. How should i write this ?

Context: i'm converting a ThreeJS implementation into Vue/Typescript. It includes a controls panel composed with slider inputs controlling parameters of the Three canvas. I separated this massive monolithic original code into 3 components : - child1: the controlsPanel, contains sliders and the reset button - child2: the Vue-GL canvas, emitting mouse events - parent: the component hosting initial data, and reseting.

parent :

<template>
  <div>
    <child1 :prop1="prop1" :prop2="prop2" :child1Prop="child1Prop" :reset="reset" />
    <child2 :prop1="prop1" :prop2="prop2" :child2Prop="child2Prop" />
  </div>
</template>

<script lang="ts">
  import { Component, Vue } from 'vue-property-decorator';

  import Child1 from './components/Child1.vue';
  import Child2 from './components/Child2.vue';

  const initialState = {
    prop1: 1,
    prop2: 2,
    child1Prop: 'some text',
    child2Prop: 'another text',
  }
  export type InitialState = typeof initialState;

  @Component({
    components: {
      Child1,
      Child2,
    },
  })
  export default class App extends Vue {
    public prop1!: InitialState['prop1'];
    public prop2!: InitialState['prop2'];
    public child1Prop!: InitialState['child1Prop'];
    public child2Prop!: InitialState['child2Prop'];

    public beforeMount() {
      Object.assign(this, initialState);
    }

    public reset() {
      Object.assign(this, initialState);
    }
  }
</script>

Child code :

<template>
...
<!-- this button is only in Child1 -->
<button type="button" @click="resetCamera">Reset</button>
</template>

<script lang="ts">
  // import VueAsyncComputed, { IAsyncComputedProperty } from 'vue-async-computed';
  import { Component, Prop, Vue } from 'vue-property-decorator';
  import { InitialState } from '../App.vue';

  @Component
  export default class ChildX extends Vue {
    @Prop() public prop1!: InitialState['prop1'];
    @Prop() public prop2!: InitialState['prop2'];
    @Prop() public childXProp!: InitialState['childXProp']; // Specific prop

    // computed getters and methods using this.prop1 etc...

    // this method is only in Child1
    public resetCamera() {
      this.reset();
    }
  }
</script>

Properties prop1 and prop2 are controlled by Child1 component and consumed by Child2. Child2 can also update these props (via mouse events), which should update sliders in Child1 appropriately.

I managed to make Typescript happy, but at the cost of typings everywhere...

Question1: is there a way to simplify while keeping the 2way-bindings between childs and parent App ? (2way-bindings don't work with above code)

Question2: How to reset all props ? my child1.resetCamera seems to call parent reset() but props are not reseting ...


Solution

  • When you use props, you should be careful about the point of this kind of data: its main purpose is simply to pass data from parent to child, that is it.

    As you might figure that out later, usually it is not a good idea to change props both in parent and child. Why? Consider the following example:

    A parent component called mainComponent passes currentID to all its children: headerComponent and footerComponent. However, if you have designed your props to be changed by both parent and children, if footerComponent changes currentID it also changes in headerComponent which may not be expected. How about a third child component being added to your parent component that uses currentID as well? It will be affected too.

    Using the above example, my suggestion is: use props in only one-way binding. In other words, mainComponent passes currentID to all its children. If any of these needs to change that props, emit an event by using $emit("updateCurrentID", newCurrentID) and catching this up in the mainComponent and, finally, change currentID and that will reflect on all children. But now, you are sure you are updating this props data in only one place (mainComponent).

    To emit that, I can think of two possibilities: by $emit itself (docs) or by creating your own custom events, as you can see here - a tutorial by MDN.

    Hope it helps