Related
I have a Vue component to display a list of data. It should receive the data and sortBy as props and render correctly. However, I have an extra button in the component that I want to update sortBy and re-render the new list but I don't know how to assign new data to computed property sortedData. Thanks a lot if I can have some advices.
<template>
<div>
<template v-for="(item, index) in sortedData" :key="index">
{{ item.name }}
</template>
<button #click.prevent="sortWith('color')">sort Color</button>
</div>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
},
methods: {
sort(array, sortBy) {
return array.sort(function (a, b) {
return b[sortBy] - a[sortBy]
})
},
sortWith(sortBy) {
// Need to sort the list and re-render new order
}
}
computed: {
sortedData() {
return this.sort(this.data, this.sortBy)
}
}
}
</script>
Try like following snippet:
const app = Vue.createApp({
data() {
return {
items: [{name: 'aaa', color: 'red'}, {name: 'ccc', color: 'purple'}, {name: 'fff', color: 'yellow'}, {name: 'bbb', color: 'blue'}, {name: 'eee', color: 'green'}],
sortingBy: 'name',
sortingOrder: false
}
},
methods: {
setSorting(item) {
this.sortingBy = item
this.sortingOrder = !this.sortingOrder
}
}
})
app.component('child', {
template: `
<div>
<template v-for="(item, index) in sortedData" :key="index">
<div>{{ item.name }} - {{ item.color }}</div>
</template>
<div class="sort">
<p>Sort By (child) :</p>
<div v-for="(title, i) in Object.keys(data[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
</div>
`,
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
sortOr: {
type: String,
default: false
},
},
data() {
return {
sortField: this.sortBy,
sortOrder: this.sortOr
}
},
methods: {
sort(array, sortBy) {
return array.sort((a, b) => {
let x = a[sortBy].toLowerCase()
let y = b[sortBy].toLowerCase()
if (this.sortOrder) {
return x > y ? -1 : 1;
} else {
return x > y ? 1 : -1;
}
return 0
})
},
sorting(item) {
this.sortField = item
this.sortOrder = !this.sortOrder
}
},
computed: {
sortedData() {
return this.sort(this.data, this.sortField)
}
},
watch: {
sortBy: function (val) {
this.sortField = val
},
sortOr: function (val) {
this.sortOrder = val
},
}
})
app.mount('#demo')
.sort {
display: flex;
align-items: center;
}
.fields {
margin-left: 1em;
}
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="sort">
<p>Sort By (parent) :</p>
<div v-for="(title, i) in Object.keys(items[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
<child :data="items" :sort-by="sortingBy" :sort-or="sortingOrder"></child>
</div>
I'm using Vue.js 2 and I'm trying to update the description of a file using an input in a child component. I've been reading a few related questions and read some of the official docs along with .sync but I'm struggling to get the result I want since files is a list of objects.
Here's what I've been trying.
Vue.component('myComponent', {
props: ["file"],
data() {
return {
myDescription: '',
}
},
mounted() {
this.myDescription = this.file.description;
},
template: `
<div>
<label>{{ file.name }}</label>
<br>
<input type="text" #input="update" :value="myDescription"></input>
<br><br>
</div>
`,
methods: {
update() {
this.$emit("update-description", this.myDescription, this.file);
},
}
})
var app = new Vue({
el: '#app',
methods: {
updateDescription(description, file) {
console.log(description);
}
},
data: {
files: [{
id: 1,
name: "Hello",
description: "",
},
{
id: 2,
name: "World",
description: "Foo",
},
{
id: 3,
name: "John",
description: "Bar",
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div> {{ files }} </div>
<br>
<my-component v-for="file in files" :key="file.id" :file="file" #update-description="updateDescription" />
</div>
You're almost there, you can see in the code you've provided that the child component event is being emitted but the value is empty. The problem is you're not updating myDescription, if you change your :value to v-model then it will update, as v-model uses two way binding.
Also, if you want to update the file description, you can just do:
file.description = description;
Vue.component('myComponent', {
props: ["file"],
data() {
return {
myDescription: '',
}
},
mounted() {
this.myDescription = this.file.description;
},
template: `
<div>
<label>{{ file.name }}</label>
<br>
<input type="text" #input="update" v-model="myDescription"></input>
<br><br>
</div>
`,
methods: {
update() {
this.$emit("update-description", this.myDescription, this.file);
},
}
})
var app = new Vue({
el: '#app',
methods: {
updateDescription(description, file) {
console.log(description);
file.description = description;
}
},
data: {
files: [{
id: 1,
name: "Hello",
description: "",
},
{
id: 2,
name: "World",
description: "Foo",
},
{
id: 3,
name: "John",
description: "Bar",
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div> {{ files }} </div>
<br>
<my-component v-for="file in files" :key="file.id" :file="file" #update-description="updateDescription" />
</div>
I am working on the table content which has 5 rows . few rows content description is same so I need to show only one row in this case and give expan button. when expand button is clicked it should show all the rows which has the same associated description. I am pasting the screenshot which I got as output .
In the above screenshot I've got the "-" button for all the rows which has same description. but I need only one "-"(collapse) button for "paytm" and one "-"button for "Paypal". and when they are clicked only one paytm, PayPal should be displayed.
let rows = [
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/04/2021' }
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/03/2020' }
},
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2021' }
},
{
id: { value: '' },
description: { value: 'Gpay' },
DueDate: { value: '04/03/2020' }
},
];
I am showing the table based on the lasted date and check if there exists any multiple same descriptions and putting them all in one object.
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = [
...(acc[current.description.value] || []),
current,
];
return acc;
}, {});
console.log(descriptionSortedArray);
and transforming the object based on latest date
const transformedRows = Object.keys(descriptionSortedArray).reduce(
(acc, current) => {
acc[current] = sortRowsByDate(descriptionSortedArray[current]);
return acc;
},
{}
);
// console.log(Object.keys(descriptionSortedArray));
console.log({ transformedRows });
and getting the key values for them by using object.keys and mapping over them.
x.[paytm:[], Gpay:[], PayPal :[]];
based on the inner array key length I am showing button (expand and collapse)if
x[paytm]>1 ?show button: without button
code is below
import React, { Component } from 'react';
import './style.css';
export default class App extends React.Component {
render() {
let rows = [
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/04/2021' }
},
{
id: { value: '' },
description: { value: 'paypal' },
DueDate: { value: '04/03/2020' }
},
{
id: { value: '' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2021' }
},
{
id: { value: '' },
description: { value: 'Gpay' },
DueDate: { value: '04/03/2020' }
},
];
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = [
...(acc[current.description.value] || []),
current,
];
return acc;
}, {});
console.log(descriptionSortedArray);
const sortRowsByDate = (rows) =>
rows.sort(
(a, b) => new Date(b.DueDate.value) - new Date(a.DueDate.value)
);
const transformedRows = Object.keys(descriptionSortedArray).reduce(
(acc, current) => {
acc[current] = sortRowsByDate(descriptionSortedArray[current]);
return acc;
},
{}
);
// console.log(Object.keys(descriptionSortedArray));
console.log({ transformedRows });
return (
<div>
<table>
<tr>
<th>id</th>
<th>description</th>
<th>duedate</th>
<th></th>
</tr>
{Object.keys(transformedRows).map((rowKey) => {
// console.log("rowKey===", rowKey)
// console.log(transformedRows[rowKey])
return (
<tbody>
{transformedRows[rowKey].length > 1
? transformedRows[rowKey].map((obj) => (
<tr>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td>{<button>-</button>}</td>
</tr>
))
: transformedRows[rowKey].map((obj) => (
<tr>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td></td>
</tr>
))}
</tbody>
);
})}
</table>
</div>
);
}
}
Please help in this. I need to show only one collapse button for the rows having same description(paytm is repeated show them only in one row give "expand" and "collapse" button). when even button is clicked it should be toggled. Please help
You can keep another field called visible along with your data array and toggle its value when clicked on the button.
Define a state to store the transformedRows
state = {
transformedRows: {}
};
Do the transformation like below in componentDidMount.
componentDidMount = () => {
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = {
...acc[current.description.value],
data: [...(acc[current.description.value]?.["data"] ?? []), current],
visible: false
};
return acc;
}, {});
const sortRowsByDate = (rows) =>
rows.sort(
(a, b) => new Date(b.DueDate.value) - new Date(a.DueDate.value.data)
);
const transformedRows = Object.keys(descriptionSortedArray).reduce(
(acc, current) => {
acc[current] = {
...descriptionSortedArray[current],
data: sortRowsByDate(descriptionSortedArray[current]["data"])
};
return acc;
},
{}
);
this.setState({ transformedRows });
};
Toggle the visible state when clicking on the button.
handleToggle = (entry, visibility) => {
this.setState((prevState) => {
return {
...prevState,
transformedRows: Object.fromEntries(
Object.entries(prevState.transformedRows).map(([key, value]) => {
if (key === entry) {
return [key, { ...value, visible: visibility }];
} else {
return [key, value];
}
})
)
};
});
};
Render rows as below.
<tbody>
{Object.entries(transformedRows).map(([key, { data, visible }]) => {
if (data.length > 1) {
return data.map((item, index) => (
<tr>
{(index === 0 || (index >= 1 && visible)) && (
<>
<td>{item.id.value}</td>
<td>{item.description.value}</td>
<td>{item.DueDate.value}</td>
</>
)}
{index === 0 && (
<td>
{
<button
onClick={() => {
this.handleToggle(key, !visible);
}}
>
toggle
</button>
}
</td>
)}
</tr>
));
} else {
return data.map(item => (
<tr>
<td>{item.id.value}</td>
<td>{item.description.value}</td>
<td>{item.DueDate.value}</td>
</tr>
));
}
})}
</tbody>
Create an accordion component as follow:
React accordion
Then use it as follow:
return (
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>description</th>
<th>duedate</th>
<th></th>
</tr>
</thead>
{Object.keys(transformedRows).map((rowKey) => {
// console.log("rowKey===", rowKey)
// console.log(transformedRows[rowKey])
console.log(rowKey);
return (
<tbody key={rowKey}>
{transformedRows[rowKey].length > 1 ? (
<tr>
<td colSpan="4">
<Accordion label={rowKey}>
{transformedRows[rowKey].map((obj) => (
<div key={obj.id.value}>
<span>{obj.id.value}</span>
<span>{obj.description.value}</span>
<span>{obj.DueDate.value}</span>
<span>{<button>-</button>}</span>
</div>
))}
</Accordion>
</td>
</tr>
) : (
transformedRows[rowKey].map((obj) => (
<tr key={obj.id.value}>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td></td>
</tr>
))
)}
</tbody>
);
})}
</table>
</div>
);
Full code:
let rows = [
{
id: { value: '1' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '2' },
description: { value: 'paypal' },
DueDate: { value: '04/04/2021' },
},
{
id: { value: '3' },
description: { value: 'paypal' },
DueDate: { value: '04/03/2020' },
},
{
id: { value: '4' },
description: { value: 'Paytm' },
DueDate: { value: '04/03/2021' },
},
{
id: { value: '5' },
description: { value: 'Gpay' },
DueDate: { value: '04/03/2020' },
},
];
const descriptionSortedArray = rows.reduce((acc, current) => {
acc[current.description.value] = [...(acc[current.description.value] || []), current];
return acc;
}, {});
console.log(descriptionSortedArray);
const sortRowsByDate = (rows) =>
rows.sort((a, b) => new Date(b.DueDate.value) - new Date(a.DueDate.value));
const transformedRows = Object.keys(descriptionSortedArray).reduce((acc, current) => {
acc[current] = sortRowsByDate(descriptionSortedArray[current]);
return acc;
}, {});
return (
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>description</th>
<th>duedate</th>
<th></th>
</tr>
</thead>
{Object.keys(transformedRows).map((rowKey) => {
// console.log("rowKey===", rowKey)
// console.log(transformedRows[rowKey])
console.log(rowKey);
return (
<tbody key={rowKey}>
{transformedRows[rowKey].length > 1 ? (
<tr>
<td colSpan="4">
<Accordion label={rowKey}>
{transformedRows[rowKey].map((obj) => (
<div key={obj.id.value}>
<span>{obj.id.value}</span>
<span>{obj.description.value}</span>
<span>{obj.DueDate.value}</span>
<span>{<button>-</button>}</span>
</div>
))}
</Accordion>
</td>
</tr>
) : (
transformedRows[rowKey].map((obj) => (
<tr key={obj.id.value}>
<td>{obj.id.value}</td>
<td>{obj.description.value}</td>
<td>{obj.DueDate.value}</td>
<td></td>
</tr>
))
)}
</tbody>
);
})}
</table>
</div>
);
i have been import JSON in my JS file, and i want to display it using a button in my table but, the JSON file its a list of users, and i want to add one user on each click.
I've been trying so many codes with no success, i got to see that something happend in my console.log() but i never got to see it on the web.
i hope the question its clear to understand..
Does anybody have an idea??
my code:
imports + JSON
import React, { Component } from 'react';
import InitialData from '../../JSON/InitialData';
import AdditionalData from '../../JSON/AdditionalData';
const { supportRequest } = InitialData;
const { users } = AdditionalData;
class SupportRequest extends Component {
state = {
text: "Send",
users: [
{
name: "Bob Sacamento",
email: "bob_sacamento#gottlieb.ca",
timestamp: "2012-04-23T11:06:43.511Z",
phoneNumber: "214-300-5846",
city: "Long Island",
status: "unsent"
},
{
name: "Hal Kitzmiller",
email: "hal_kitzmiller#lindgren.com",
timestamp: "2012-04-23T08:22:43.511Z",
phoneNumber: "057-812-4000",
city: "The Bronx",
status: "unsent"
},
{
name: "Bob Cobb",
email: "bob.cobb#nelson.tv",
timestamp: "2012-04-23T14:22:43.511Z",
phoneNumber: "866-668-0327",
city: "Florence",
status: "unsent"
},
{
name: "Mike Moffett",
email: "mike_moffett#kaia.org",
timestamp: "2012-04-23T07:22:43.511Z",
phoneNumber: "647-851-1333",
city: "Upper East Side",
status: "unsent"
}
]
}
the Add button:
addUser = () => {
const array = this.state.users;
array.map((arr) => {
return (users.push({ users: arr[0] })
)
})
}
render() {
return (
<div>
<div style={{ fontSize: "25px" }} className="jsonData-container">Support Request</div>
<button
style={{ variant: "contained", alignContent: "flex-end" }}
onClick={() => this.addUser
}
>
Add
</button>
Rest of the code:
<div className="container" ></div>
<table className="table table-bordered table-striped table-hover">
<thead style={{ backgroundColor: "black", color: "white" }}>
<tr>
<th>name</th>
<th>email</th>
<th>timestamp</th>
<th>phoneNumber</th>
<th>city</th>
<th>status</th>
</tr>
</thead>
<tbody>
{supportRequest.map((supportRequest, index) => {
return (
<tr key={index}>
<td >
{supportRequest.name}
</td>
<td>
{supportRequest.email}
</td>
<td>
{supportRequest.timestamp}
</td>
<td>
{supportRequest.phoneNumber}
</td>
<td>
{supportRequest.city}
</td>
<td>
<button
onClick={() => {
this.setState({ text: 'send' })
console.log(this.setState({}))
}}>
{supportRequest.status}
</button>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}
}
export default SupportRequest;
you can use React.Children from React React.Children. As you have already retrieved the user data, you don't have to create new list of users on each click. You can simply control the view.
class App extends Component {
state = {
text: "Send",
users: [
{
name: "Bob Sacamento",
email: "bob_sacamento#gottlieb.ca",
timestamp: "2012-04-23T11:06:43.511Z",
phoneNumber: "214-300-5846",
city: "Long Island",
status: "unsent",
},
{
name: "Hal Kitzmiller",
email: "hal_kitzmiller#lindgren.com",
timestamp: "2012-04-23T08:22:43.511Z",
phoneNumber: "057-812-4000",
city: "The Bronx",
status: "unsent",
},
{
name: "Bob Cobb",
email: "bob.cobb#nelson.tv",
timestamp: "2012-04-23T14:22:43.511Z",
phoneNumber: "866-668-0327",
city: "Florence",
status: "unsent",
},
{
name: "Mike Moffett",
email: "mike_moffett#kaia.org",
timestamp: "2012-04-23T07:22:43.511Z",
phoneNumber: "647-851-1333",
city: "Upper East Side",
status: "unsent",
},
],
usersToShow: 3,
};
onLoadMore = () => {
this.setState({
usersToShow: this.state.usersToShow + 1,
});
};
onViewLess = () => {
this.setState({
usersToShow: this.state.usersToShow - 1,
});
};
showUerData = () =>
this.state.users.map((user, i) => <p key={i}>{user.name}</p>);
render() {
return (
<div className="App">
{React.Children.toArray(this.showUerData()).slice(
0,
this.state.usersToShow
)}
{this.state.usersToShow === this.state.users.length ? (
<button onClick={this.onViewLess}>View Less</button>
) : (
<button onClick={this.onLoadMore}>Load More</button>
)}
</div>
);
}
}
I'm passing some dynamic data from a parent component to a child component using props .. So I would like to know how I can add myColor prop to total value and show it an render the result in a final value.
I've already update the post with the parent component (shapes) and the child component (colors)
I'm using Vue 2 and webpack.
//parent component
<v-layout row wrap primary-title v-for="shape in shapes" :key="shape.id">
<v-layout column>
<v-flex >
<v-subheader>{{shape.name}} {{shape.price}}€ {{selectedShape.price}}</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<my-colors :myShape="selectedShape.price"></my-colors>
<script>
import Colors from './Colors.vue';
export default {
components: {
Colors
},
data() {
return {
selectedShape: {},
shapes: [{
id: 1,
name: "Square",
price: 4,
href: "../../static/square.jpg"
}, {
id: 2,
name: "Circle",
price: 6,
href: "../../static/circle.jpg"
}]
}
},
computed: {
totalShape: function() {
var totalShape = 0;
for (var i = 0; i < this.shapes.length; i++) {
if (this.shapes[i].selected) {
totalShape += this.shapes[i].price;
}
}
return totalShape;
}
},
methods: {
getSelectedShape() {
return this.selectedShape;
},
}
}
</script>
//child component
<v-layout>
<v-layout>
<v-flex >
<h3 >Total price:</h3>
</v-flex>
</v-layout>
<v-layout>
<v-flex
<v-subheader> {{total}} {{myShape}} €</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<script>
export default {
props: ['myShape'],
data: () => ({
checked1: '',
showCart: false,
colors: [{
id: 1,
name: "white",
price: 2,
checked: '',
}, {
id: 2,
name: "black",
price: 2.0,
checked: '',
}, {
id: 3,
name: "Grey",
price: 2.25,
checked: '',
}, {
id: 4,
name: "Blue",
price: 1.6,
checked: '',
}, {
id: 5,
name: "Red",
price: 2.5,
checked: '',
}, {
id: 6,
name: "Yellow",
price: 2.75,
checked: '',
}],
}),
computed: {
total: function() {
var total = 0;
for (var i = 0; i < this.colors.length; i++) {
if (this.colors[i].checked) {
total += this.colors[i].price;
}
}
return total;
},
},
}
</script>
I do not understand your needs from this script, but be aware of one way data flow in Vue. So, you can send data from parent component to child component in which its will be accessible through props, but not from child component to parent. Use Vuex if you need two-way data flow between components.
var child = {
template: '#child',
props: ['fromParent']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['fromInstance']
})
new Vue({
el: '#app',
data: {
instanceData: {
text: 'Original value'
}
},
created () {
var self = this
setTimeout(_ => self.instanceData.text = 'Changed value', 3000)
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :from-instance="this.instanceData"></parent>
</div>
<template id="parent">
<div>
<child :from-parent="this.fromInstance"></child>
</div>
</template>
<template id="child">
<p>{{this.fromParent.text}}</p>
</template>
Example with SELECT:
var child = {
template: '#child',
props: ['selected']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['options'],
data () {
return {
parentCar: 'none'
}
},
methods: {
update (e) {
this.parentCar = e.target.value
}
}
})
new Vue({
el: '#app',
data: {
items: {
audi: 'Audi',
bmw: 'BMW',
mercedes: 'Mercedes',
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :options="this.items"></parent>
</div>
<template id="parent">
<div>
<select #change="update">
<option value="none" selected>Car</option>
<option v-for="(value, key) in options" :value="key">
{{ value }}
</option>
</select>
<child :selected="this.parentCar"></child>
</div>
</template>
<template id="child">
<p>{{ selected }}</p>
</template>
Example with checked / unchecked checkbox:
var child = {
template: '#child',
props: ['checked']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
data () {
return {
checkbox: false
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent></parent>
</div>
<template id="parent">
<div>
<input type="checkbox" v-model="checkbox">
<child :checked="this.checkbox"></child>
</div>
</template>
<template id="child">
<p>{{ checked }}</p>
</template>