how to change props value using method - Vue.js - javascript

I am quite new in vue and working on a task which is based on vue.js. I am showing my data in a component using a prop. Now I want to add a method to increment the quantity of a product.
here is my code:
<div v-for="(products, index) in products">
<mdc-layout-cell span="2" align="middle">
{{ products.product_barcode }}
</mdc-layout-cell>
<mdc-layout-cell span="2" align="middle">
{{ products.product_quantity}}
</mdc-layout-cell>
<i class="mdc-icon-toggle material-icons float-left"
aria-pressed="false"
v-on:click="incrementItem(index)">
add
</div>
here is my JS:
export default {
props: [
'products',
],
methods: {
incrementItem(index) {
let item = this.products[index];
this.products[index].product_quantity =
this.products[index].product_quantity + 1;
console.log(this.products[index].product_quantity);
},
}
I can see the incremented value in the console, but the value is not increasing in the respective row. How could I increment the value of product_quantity? Any help would be highly appreciable

You can assign props value to a variable. Then use it child component.
export default {
props: {
products: Array,
},
data(){
return {
newProducts: this.products,
}
}
}
You should change props structure in child component. use newProducts in child component and if you update parent props data you should use emit for child to parent communication.
For further parent child communication you can follow this tutorial:
https://www.bdtunnel.com/2018/02/vue-js-component-communication-complete.html

First, in terms of vue flow, remember never mutating directly props. You should mutate the data in the parent component instead. To do this, the recommendation is to create a copy of props in children data so when click at the button, the children data change -> the parents data change, which makes the children props also change. There are many ways to do this. I dont have you parents component code so i make a generic code below, you can follow:
using sync
in parents components
<parent :products.sync=products />
in children components methods:
data() {
return {
productListInChildren: this.products; // pass props to children inner data
}
},
methods: {
incrementItem(index) {
//do modification in productListInChildren
let item = this.productListInChildren[index];
this.productListInChildren[index].product_quantity =
this.productListInChildren[index].product_quantity + 1;
// update it back to parents
this.$emit('update:products', this.productListInChildren)
}
}
<div v-for="(products, index) in productListInChildren"> // use productListInChildren instead
<mdc-layout-cell span="2" align="middle">
{{ products.product_barcode }}
</mdc-layout-cell>
<mdc-layout-cell span="2" align="middle">
{{ products.product_quantity}}
</mdc-layout-cell>
<i class="mdc-icon-toggle material-icons float-left"
aria-pressed="false"
v-on:click="incrementItem(index)">
add </i>
</div>
Second: in terms of code design, it is recommended in your case, the children should only handle the logic of display (dumb component). Logic of changing data should be moved to parents (like a controller). If that is the case. you can create a method in parents component and add the increment logic:
parents components
<parent :products=products #increment="incrementInParent"/>
methods: {
incrementInParent() {// move the logic here}
}
children components
methods: {
incrementItem(index) {
// call the methods in parents
this.$emit("increment");
}
}

First of all, you should never mutate props in the child component. You should use event communication pattern for child and parent communication.
While changing a particular value inside an array in particular index in an array or updating a property in an object the view does not react to these changes due to Object and Array Caveats in Vue.
You can read it here
https://v2.vuejs.org/v2/guide/reactivity.html
You can use $set to make the template react to changes in Object and Array.
Here is my sandbox URL which answers your question

Related

vue - $emit vs. reference for updating parent data

We need to use $emit to update the parent data in a vue component. This is what has been said everywhere, even vue documentation.
v-model and .sync both use $emit to update, so we count them $emit here
what I'm involved with is updating the parent data using reference type passing
If we send an object or array as prop to the child component and change it in the child component, changes will be made to the parent data directly.
There are components that we always use in a specific component and we are not going to use them anywhere else. In fact, these components are mostly used to make the app codes more readable and to lighten the components of the app.
passing reference type values as prop to children for directly change them from children is much easier than passing values then handle emitted event. especially when there are more nested components
code readability is even easier when we use reference type to update parent.
For example, suppose we have grand-parent, parent and child components. in parent component we have a field that change first property of grand-parent data and in child component we have another field that change second property of grand-parent data
If we want to implement this using $emit we have something like this : (we are not using .sync or v-model)
// grand-parent
<template>
<div>
<parent :fields="fields" #updateFields="fields = $event" >
</div>
</template>
<script>
import parent from "./parent"
export default {
components : {parent},
data(){
return {
fields : {
first : 'first-value',
second : 'second-value',
}
}
}
}
</script>
// parent
<template>
<div>
<input :value="fields.first" #input="updateFirstField" />
<child :fields="fields" #updateSecondField="updateSecondField" >
</div>
</template>
<script>
import child from "./child"
export default {
components : {child},
props : {
fields : Object,
},
methods : {
updateFirstField(event){
this.$emit('updateFields' , {...this.fields , first : event.target.value})
},
updateSecondField(value){
this.$emit('updateFields' , {...this.fields , second : value})
}
}
}
</script>
// child
<template>
<div>
<input :value="fields.first" #input="updateSecondField" />
</div>
</template>
<script>
export default {
props : {
fields : Object,
},
methods : {
updateFirstField(event){
this.$emit('updateSecondField' , event.target.value)
},
}
}
</script>
Yes, we can use .sync to make it easier or pass just field that we need to child. but this is basic example and if we have more fields and also we use all fields in all component this is the way we do this.
same thing using reference type will be like this :
// grand-parent
<template>
<div>
<parent :fields="fields" >
</div>
</template>
<script>
import parent from "./parent"
export default {
components : {parent},
data(){
return {
fields : {
first : 'first-value',
second : 'second-value',
}
}
}
}
</script>
// parent
<template>
<div>
<input v-model="fields.first" />
<child :fields="fields" >
</div>
</template>
<script>
import child from "./child"
export default {
components : {child},
props : {
fields : Object,
}
}
</script>
// child
<template>
<div>
<input v-model="fields.second" />
</div>
</template>
<script>
export default {
props : {
fields : Object,
}
}
</script>
as you see using reference type is much easier. even if there was more fields.
now my question :
should we use reference type for updating parent data or this is bad approach ?
even if we use a component always in the same parent again we should not use this method ?
what is the reason that we should not use reference type to update parent?
if we should not use reference type why vue pass same object to children and not clone them before passing ? (maybe for better performance ?)
The "always use $emit" rule isn't set in stone. There are pros and cons of either approach; you should do whatever makes your code easy to maintain and reason about.
For the situation you described, I think you have justified mutating the data directly.
When you have a single object with lots of properties and each property can be modified by a child component, then having the child component mutate each property itself is fine.
What would the alternative be? Emitting an event for each property update? Or emitting a single input event containing a copy of the object with a single property changed? That approach would result in lots of memory allocations (think of typing in a text field emitting a cloned object for each keypress). Having said that, though, some libraries are designed for this exact purpose and work pretty well (like Immutable.js).
For simple components that manage only small data like a textbox with a single string value, you should definitely use $emit. For more complex components with lots of data then sometimes it makes sense for the child component to share or own the data it is given. It becomes a part of the child component's contract that it will mutate the data in certain circumstances and in some particular way.
what is the reason that we should not use reference type to update parent?
The parent "owns" the data and it knows that nobody but itself will mutate it. No surprises.
The parent gets to decide whether or not to accept the mutation, and can even modify it on-the-fly.
You don't need a watcher to know when the data is changed.
The parent knows how the data is changed and what caused the change. Imagine there are multiple ways that the data can be mutated. The parent can easily know which mutation originated from a child component. If external code (i.e. inside a child component) can mutate the data at any time and for any reason, then it becomes much more difficult for the parent to know what caused the data to change (who changed it and why?).
if we should not use reference type why vue pass same object to children and not clone them before passing ? (maybe for better performance ?)
Well yes, for performance, but also many other reasons such as:
Cloning is non-trivial (Shallow? Deep? Should the prototype be copied too? Does it even make sense to clone the object? Is it a singleton?).
Cloning is expensive memory- and CPU-wise.
If it were cloned then doing what you describe here would be impossible. It would be silly to impose such a restrictive rule.
#Vue Detailed usage of $refs, $emit, $on:
$refs - parent component calls the methods of the child component. You can pass data.
$emit - child components call methods of the parent component and pass data.
$on - sibling components pass data to each other.

Vue.js component within component, manipulate parent data

ive seen afew answers that sort of answer my question but not fully, so let me explain what I want to do.
We use a global #app div within the layout of our website, which is a Laravel project. So all pages will be the same main Vue instance, due to this i'm separating key functionality into components.
So, the first example is just a simple Tab component, this either separates any children into tabs, or accepts some data which the single child component then renders.
So below i'm injecting some data from another component, this ajax component literally just does an ajax call, and makes the data available within it's slot.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
#{{ row['example' }}
</div>
</div>
</tabs>
</div>
</ajax>
Now this is all well and good, to a point, but this falls down with the below code. This contains a component which will allow the used to drag and drop elements, it re-arranges them by literally moving the data around and letting Vue handle the DOM changes.
This will of course work fine within it's own data which you have injected in, but when you change the data within the component below this then clears this child component.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
<draggable :injected="row">
<div>
<div v-for="item">
#{{ item }}
</div>
</div>
</draggable>
</div>
</div>
</tabs>
</div>
</ajax>
I need to find a way to make any changes to this data apply to the parent data, rather than the data passed into the child components.
What is the best practice to do this!?
Edit 1
Basically, I need any child component's manipulate the data within the ajax component. The children within ajax could change, or there could be more, so I just need them all to do this without knowing what order or where they are.
It is hard to come up with specifics on this one, but I am going to try to put you in the right direction. There are three ways to share data between components.
1) Passing down data via props, emitting data up via custom events
The passing down of data via props is a one-way street between the parent and child components. Rerendering the parent component will also re-render the child and data will be reset to the original state. See VueJS: Change data within child component and update parent's data.
2) Using a global event-bus
Here you create an event bus and use this to emit the data to different components. All components can subscribe to updates from the event bus and update their local state accordingly. You initiate an event bus like this:
import Vue from 'vue';
export const EventBus = new Vue();
You send events like this:
import { EventBus } from './eventbus.js'
EventBus.$emit('myAwsomeEvent', payload)
And you subscribe to events like this:
import { EventBus } from './eventbus.js'
EventBus.$on('myAwsomeEvent', () => {
console.log('event received)
})
You still need to manage state in the components individually. This is a good start with an Event bus: https://alligator.io/vuejs/global-event-bus/
3) Using Vuex
Using Vuex extracts the component state into the Vuex store. Here you can store global state and mutate this state by committing mutations. You can even do this asynchonously by using actions. I think this is what you need, because your global state is external to any components you might use.
export const state = () => ({
resultOfAjaxCall: {}
})
export const mutations = {
updateAjax (state, payload) {
state.resultOfAjaxCall = payload
}
}
export const actions= {
callAjax ({commit}) {
const ajax = awaitAjax
commit('updateAjax', ajax)
}
}
Using vuex you keep your ajax results separated from your components structure. You can then populate your state with the ajax results and mutate the state from your individual components. This way, it doesn't matter whether you recall ajax, or destroy components since the state will always be there. I think this is what you need. More info on Vuex here: https://vuex.vuejs.org/

VueJS XHR inside reusable component

Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.

Passing a value to the parent component

How could a child component pass its value to the parent component? Here is my child component:
Javascript:
new Vue({
el: '#table-list',
data: {
tableList: ['Empty!'],
tableSelected: ""
},
methods: {
getTableList() {
axios
.get('/tables')
.then(tableList => {
this.tableList = tableList.data;
})
.catch(e => console.warn('Failed to fetch table list'));
},
selectTable(table) {
this.tableSelected = table;
}
},
mounted() {
this.getTableList();
}
});
HTML:
<div id="table-list">
<p v-for="table in tableList">
<i class="fa fa-table" aria-hidden="true"></i>
<span class="text-primary" v-on:click="selectTable(table)"> {{ table }} </span>
</p>
</div>
When on click, selectTable is called, I want to show the value in its parent component? i.e I need to pass tableSelected property to the parent component. How could I do this?
You should use vue components, specifically events mechanism for what you want to archive.
Props are for pass data from parent to a child components, and events to send messages from child component to parent.
We have learned that the parent can pass data down to the child using props, but how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Please see this fiddle https://jsfiddle.net/AldoRomo88/sLo1zx5b/
I have changed your selectTable method to emit a custom event
selectTable: function(table) {
this.$emit('item-changed',table);
}
And in your parent component you just need to listen for that event
<div>
{{selectedItem}}
</div>
<table-list #item-changed="newValue => selectedItem = newValue " ></table-list>
Let me know if you need more clarification.
Here is the page that explains how children emit events to listening parents.
Here is the page on managing state.
Remember what you're aiming for, with VUE, is MVVM. You want all your state in a store, where each item of state is stored once, regardless of how many times it's referenced, and how many ways it can be updated.
Your tableSelected is an item of state. You can pass state changes up the chain if you need to, so long as they finish in a store, not in a component or a vue. But you can keep it simple: make tableSelected a property in your store, and declare it directly in the data element of components that need it. If you want to be rigorous, put a changeTableSelected() method on the store.
You need to start worrying about props and events if one component will have many instances, or if a component knows nothing about the page on which it will appear. Until that time, I would prefer using data and the store.

jquery vs react.js remove item from a list

const RenderItem = (props) => {
return(
<ul id="todo">
{props.items.map((item,i) =>
<li className='list-group-item' data-id={item.id} key={i}>{item.name}
<button className="btn btn-sm btn-primary" onClick={() => props.remove(item.id)}>X</button>
</li>
)}
</ul>
)
};
const TodoItems = React.createClass({
getInitialState() {
return {
items: [
{id:1,name:"Gym"},
{id:2,name:"Jump"},
{id:3,name:"Racing"}
]
}
},
remove(id){
this.setState({
items: this.state.items.filter((el) => id !== el.id)
})
},
render(){
return(
<RenderItem items={this.state.items} remove={this.remove}/>
)
}
})
ReactDOM.render(
<TodoItems />,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
<script src="https://npmcdn.com/react#latest/dist/react-with-addons.js"></script>
<script src="https://npmcdn.com/react-dom#latest/dist/react-dom.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I'm confused how stuff work here in react.js, I need presduo code how to passing work. I was from jquery background, it's so straight forward, just get the right dom and do $(this).remove(), while in react I'm confused.
<button className="btn btn-sm btn-primary" onClick={() => props.remove(item.id)}>X</button>
When you click what happened? arrow function for? and where does the remove come from?
React follows unidirectional data binding, which means, the data will flow in a single direction. So, the data is stored in a state. Whenever the state changes the component will re-render. A state can be changed at any point in time. But, mostly it will be changed on any action. In your case the action in onClick(). This will call the function will the id of the element which triggered the onClick(). Now, in that function, you will iterate through the state, identify, remove and set the new state using setState() which will re-render the component with the new data. This render will not have the clicked element as it was removed from the state.
The remove() function is passed on to the child component from the parent component which can be accessed using props. This will be a reference to the function declared in the parent component. During the click, using this reference, the function in the parent component will be called!
In react, components are rendered based on their props and state. props are passed from the parent component, and a component may or may not have internal state.
Using jQuery, you would try to remove it by removing the DOM node directly. However, in react, interacting with the DOM directly is an anti-pattern. Since the current state and props determines what is rendered, you want to change one of those that will cause a re-rendering and display something new.
In your case, it looks like you're trying to modify props directly. props are read only and should not be modified. Since props are passed down from the parent component, you would have to change what's being passed down from the parent. There are a few ways you can do that, such as passing a callback function defined in the parent component as a prop that can make such a change. But for this example, it might be easier/more applicable to use state.
Remember, you want to change the component's state to reflect what you want to see (what you want to render). So let's say you have an array called items in your component's state. And let's say you are
class ListDemo extends React.component {
constructor() {
super()
// initialize state
this.state = {
items: ['thing1', 'thing2', 'thing3']
}
}
onClick(index) {
// newItems is a new array with the element at the given index removed
// this logic doesn't handle edge cases, but that's besides the point
const newItems = [
...this.state.items.slice(0, index),
...this.state.items.slice(index + 1)
]
// here we set this component's state's items to
// the new state with what we want removed.
// it will then re-render the component to reflect the new state
this.setState({
items: newItems
})
}
render() {
return (
<div>
{
// here we render buttons for each item in the component's state's items
// we use `onClick` to make it so that clicking the button
// will remove it from the list
this.state.items.map((item, index) => {
return (
<button
onClick={() => this.onClick(index)}
key={`${item}${index}`}
>
{item}
</button>
)
})
}
</div>
)
}
}
Let's go over what is happening in this example. First, when the component is created, it's initial state is set with an items array that contains 3 elements. When it is first rendered, it will create a button for each element in the items array. Notice that what is rendered is purely determined by the state.
Each button also has a callback that is called when it's clicked (set by onClick). This callback is what will remove that specific element from the items array and then update the component's state to have a new items array without the element that was just removed. This in turn causes a re-rendering, which uses the new items array to display the buttons. This time around, the button we removed by click it is no longer there since it's no longer in this.state.items.
That is one of the key concepts of react - components are rendered based on their props and state, and changing either will update what is displayed.
As for why to use an arrow function, it's because it will automatically bind the this value to the ListDemo component. Similarly, you can use something like onClick={this.onClick.bind(this)}. Doing this is necessary since the value of this when clicking the button isn't guaranteed to what you expect. Reading this might make it more clear.

Categories

Resources