Pass object via props: Reference vs. emit value best practices

props

#1

I have some questions about best practices regarding passing an object as prop in VueJS.

I created an example to see the differences:

Questions:

  • ChildReference component: Why is using the object which is passed by reference not throwing any console errors from Vue which warn you about mutating the parents value directly?

  • ChildEmit component: Why is the the second input of the ChildEmit component (“dog”) emitting its value to the parent even though there is no @input="$emit(.. on its <input> and uses its “own” object (valueEmit)? You can try this out by putting something in the second input field, which doesn’t get emitted, but as soon as the first input field got used once, the second is emitted, too. Does using the event somehow “binds” the child’s object to the parent?

  • Is Object.assign({}, .. (or something similar like JSON.parse(JSON.stringify(..) really the standard way of using a passed object in a child component?

App.vue - parent

The parent component has two objects, valueReference & valueEmit.

  data() {
    return {
      valueReference: {},
      valueEmit: {}
    }
  },
  methods: {
    updateParentValue(newValue) {
      console.log("new value", newValue)
      this.valueEmit = newValue;
    }
  }

It also uses two different components, ChildReference & ChildEmit.

  <div>
    <ChildReference :valueReference="valueReference">
    </ChildReference>
  </div>

  <div>
    <ChildEmit :initialValue="valueEmit"
              @update-value-by-child="updateParentValue">
    </ChildEmit>
  </div>

ChildReference.vue - first child component

For comparison the first child is passed the valueReference directly via v-bind which results in passing by reference, so the parent and the child use the “same” object which is used by the child component (which is against the one-way-dataflow):

<template>
  <div class="child-reference">
   ...
    Value cat: <input type="text"
          v-model="valueReference.cat">
    <br>
    Value dog:<input type="text"
          v-model="valueReference.dog">    

  </div>
</template>

<script>
export default {
  ...
  props: {
    valueReference: Object
  },
  data() {
    return {};
  }
};
</script>

ChildEmit.vue - second child component

The second child is passed the valueEmit object, but declares an own object in its data() function which is used in the component. Also, one input emits a custom event (with the object as parameter).

<template>
  <div class="child-emit">
    Value cat: <input type="text"
          @input="$emit('update-value-by-child', valueEmit)"
          v-model="valueEmit.cat">
    <br>
    Value dog:<input type="text"
          v-model="valueEmit.dog">    

  </div>
</template>

<script>
export default {
  ...
  props: {
    initialValue: Object
  },
  data() {
    return {
      valueEmit: Object.assign({}, this.initialValue)
    };
  }
};
</script>

#2

Vue can only detect direct mutations. Because the entire Object is passed and a property of that object is mutated, it doesn’t detect this. You’ll notice only warnings occur for direct mutations: https://jsfiddle.net/jamesbrndwgn/eywraw8t/415172/

Because you are emitting the entire object on the cat input event: @input="$emit('update-value-by-child', valueEmit)". This then updates the parent, but because you are then updating the value (valueEmit) used with your :initialValue prop then the child valueEmit is updated with the entire object.

Yes, though I think I’d advise against using the same object as a prop and updating it through an $emit as well. Obviously it works, but It can become quite confusing as to where the source of truth lays. A better approach may be to pass an initialValue, but update a seperate data property on the parent: https://codesandbox.io/s/q7vkmz7q54. I believe this also reduces unnecessary component rendering as the initialValue prop isn’t updated thus causing the child to re-render again.


Attempting to access nested object from component without getting all objects
#3

@JamesThomson

In other words: If you want to pass down an object to a child (which mutates the data), you never use an existing object of the parent but rather always pass down a static “dummy” object?


#4

Yes, that’s one approach if you need to pass and manipulate entire objects. I try to be more explicit, passing individual properties to stateless components which accept the value and then $emit the updated value. This way you can be sure you are only mutating that values you intend to. https://jsfiddle.net/jamesbrndwgn/eywraw8t/418055/


#5

This makes only sense for a “two-level” structure (1 parent - x childs). In my particular case, I have 1 parent with 2 childs, where 1 child has 3 childs itself (which are input fields). I need the values of these input fields in the parent component to filter data, so I have to pass down and object from parent to child and this child will pass down individual properties to the childs with input fields.


#6

I haven’t delved into your examples, but on the general theme of objects as props, bear in mind that you can also shortcut attribute markup on the parent with v-bind, and so pass the object as values:

Child:

props: {
  a: Number,
  b: Number,
  c: Number,
}

Parent:

<child v-bind="obj" />
data () {
  return {
    obj: {
      a: 1,
      b: 2,
      c: c,
    }
  }
}

#7

This will actually trigger a [Vue warn] in the console, as the child mutates the value via an input field. Also, this doesn’t help with my structure: Parent > Child 1 > Child 1.1, Child 1.2, Child 1.3, as described in my previous post.


#8

Are you using Vuex? When you find yourself having to pass objects down to grandchildren (or further) it’s often a sign that you need to implement a form of state management.


#9

Yes, I am, e. g. for application data.

This is the real use case, I want nothing more than emitting input values from the grandchildren to the parent.

Parent (fetches data from the Vuex state, has an object with keys ("filter1", "filter2", ..., "filter8")
|--  Table (generates based on passed data)
|--  FilterWrapper 1 (independent from data, gets passed filter object)  passes down object with keys for filters
|            |--  FilterComponent (external library, has input fields, emits input value)
|--  ....
|--  FilterWrapper 8
|            |--  FilterComponent 

To put this filter object in the Vuex state feels a bit “over the top” or am I too uneasy?


Well, while writing this I just realised that putting it in the state will help me to keep the filter value even when the user switches the pages, so I will definitely do this. It’s still unclear on how to do it for other use cases though.

Thanks for your time.


#10

So what is your actual use case?

It’s all a bit theoretical so far to actually provide some concrete help.

Perhaps you can describe the setup and intended outcome and we’ll try to connect the dots.


#11

What if your component is say a user form with over 100 inputs? Explicitly passing this many props seems excessive. Surly there is a better solution. Is it worth passing in computed object with all the required props?


#12

If I had a form with 100 inputs I probably wouldn’t be passing an Object as a prop. The process of updating that object would be tedious. Instead I’d probably use state management (e.g. Vuex) to get & set data. A plugin like Vuex Map Fields would probably help make the wiring less of a task.


#13

The rational for using state management is always described as a solution for syncing data that is found in multiple places at various depths within an app. I have never heard it rationalised to address overly verbose syntax to explicitly binde props (one way or two way). Consider a government service like applying applying for a licence. This process often has one or more views with maybe 10 input fields per view. It’s a relatively flat/simple data structure and the data is only bound to one view. Dosn’t reaching for vuex for such a common design pattern demonstrate a problem with vue’s syntax?


#14

Seems like a great case for Vuex. Moving between views the state for each will always persist. I don’t have experience building government websites though, so there could be a lot I’m overlooking - that said, I’ve never once been impressed by the way government sites handle form data, sooo :thinking:

I don’t think so. Vue itself solves certain problems in the best way they see fit - I can’t myself speak to why they chose to do so though. Should you need to extend anything then you can reach for other plugins/patterns. Personally this is what I like about Vue. It may have some opinions about how to do things, but you don’t have to do it that way. You can use props/emit if you like, or you can go down the Vuex route, or you could do something else - it’s up to you as the developer.


#15

Thanks for that James. I have been really struggling with implementing this design pattern without resorting to a state manager. I thought i must be missing something because passing and watching all these props has been very cumbersome. I will take your advise. I just didn’t want to resort to another tool if it wasn’t necessary.


#16

No problem. That would be very cumbersome. Unfortunately forms are (and for the foreseeable future) very tedious work. Like I said, you can use some plugins to help with the process, but because each form item needs to be unique it ends up being a lot of wiring. I think @davestewart has a fair bit of experience with this, perhaps he can chime in with his own advice.


#17

I agree.

Just scanning back to your original post; yes, I’d probably pass a data structure into the root form, and pass references to children. No emits needed, and you just JSON.stringify() the root to get all data at any time. You could even include special functions, rules or validation on the passed in class if you needed.

See how I solved something similar here:


#18

Still playing around with non vuex solutions. I have made an example on codepen that I think is pretty neat but I am just concerned it is an anti patter.

In the example I am creating local components that effectively create a tailored render for a global component. If I were to make the ‘Components’ and ‘views’ property in main view model dynamic I would should be able to insert and remove views without having to bake in the configuration.

I guess I am concerned that creating local components that utilise the $parent property is a misuse of the framework.