I've been stuck on this for a while now and i'm not sure what to Google to find a solution and through the vuejs docs but can't find anything usefull.
Basicly I have a app with a few props. One of them is 'soundpacks'. This prop is setup this way in my app.js:
soundpacks: [
{
title: 'Techno soundpack',
tempo: 130,
genre_id: 1,
teacher_id: 1
},
{
title: 'Deep House soundpack',
tempo: 123,
genre_id: 2,
teacher_id: 2
}
]
As you can see I have declared a genre_id in here. I have another prop declared like this:
genres: [
{
id: 1,
label: 'Techno'
},
{
id: 2,
label: 'Deep House'
}
]
My problem is as follows. On my front-page I want to display all the soundpacks in a div which I do with a v-for. This is working but in this div I want to display the name of the genre that is linked to the soundpack through the genre_id. I have no clue on how to get this label value to display in this v-for and i'm hoping anybody can give me a push in the good direction. I'm new to Vue.Js thats why I'm asking what looks to be a very simple problem.
Any help is appreciated.
Thank you!
I just wrote a computed property that you can add to your component.
Will compute a new list that you can directly use to display in your app.
computed: {
computeListData(){
let newList = []
this.soundpacks.forEach((item) => {
let data = this.genres.filter((genre) => {
return (genre.id === item.genre_id)
})
let listData = item
listData.label = data[0].label
newList.push(listData)
})
return newList
}
}
Here's a gist link that you can refer to:
https://gist.github.com/ayushgupta11/7eeb1a4fdfeadab1a94a1e592ecd53dd
Please tell if this worked for you.
Lots of options here.
Just using a template you can achieve this using nested loops:
<div v-for="pack in soundpacks">
{{ pack.title }} -
<template v-for="genre in genres">
<template v-if="genre.id === pack.genre_id">
{{ genre.label }}
</template>
</template>
</div>
Alternatively, you can use a computed property to add the genre label to your soundpack data:
computed: {
extendedSoundpacks () {
return this.soundpacks.map(pack => {
const genre = this.genres.find(genre => genre.id === pack.genre_id)
return {
// Copy the original object to avoid mutating it.
// Alternatively you could just nest the object by
// removing the three dots, though this would be at
// the expense of a more complicated template.
...pack,
label: genre.label
}
})
}
}
While this is slightly better than the loop in the template it still uses nested looping. Better would be to prepare a map to do a quick lookup of the label from the id:
computed: {
genreMap () {
const genreMap = {}
this.genres.forEach(genre => {
// If genres have more than just a label you could store
// the whole object instead of just the label
genreMap[genre.id] = genre.label
})
return genreMap
}
}
Now we've got genreMap, the find in my earlier extendedSoundpacks example becomes just:
const genre = this.genreMap[pack.genre_id]
Whilst the code isn't much shorter it does avoid the nested looping, which would be much faster if the arrays are large enough.
However, once you have genreMap it becomes pretty trivial to do this in the template without creating a new array of soundpacks:
{{ genreMap[pack.genre_id] }}
In this case that would be sufficient but if the logic were more complicated than a simple id/label lookup you could use a method...
{{ getGenreLabel(pack.genre_id) }}
... or a filter:
{{ pack.genre_id | genreLabel }}
These would need to be defined as functions in the methods or filters sections of your component respectively. If you needed to use this logic across multiple components you might consider registering the filter globally.
<li v-for="soundpack in soundpacks" :key="soundpack.teacher_id">
{{genres[soundpack.genre_id - 1].label}}
</li>
Note that the -1 is because you started your indexing at 1 while the index of an array starts at 0, also note that my code only works if you continue a constant numbering of your genres.
Related
I have the following React component (this is just an example):
class MainForm extends React.Component {
constructor(props) {
super(props);
this.state = {
elements : [
{
id: 1, // this is never changed
title: 'Element 1',
data: {
text: 'Initial text for Element 1',
// other properties
...
}
},
.....
]
};
}
}
The state here is an array of objects (like the one in my code). All elements in the array have id and this property is never changed!
I would like to update this array - remove elements, add new elements and the most interesting - update some existing elements in that array.
Add/Remove are easy to implement, but I got stuck with the update of the existing items.
I am new to React so here is my approach and it is probably a little expensive:
updateElements = value => {
// value is the object with some property updated
/* example (data.text is updated)
{
id: 1,
title: 'Element 1',
data: {
text: 'Updated text for Element 1',
// other properties
...
}
}
*/
// there is 'id' for each item so I do map to replace out-dated element with a new one
const updatedElements = this.state.elements.
.map(e => {
if (e.id === value.id) {
return value;
} else {
return e;
}
});
this.setState({
elements: updatedElements
});
}
This array in my case can be pretty big as well as objects stored in it.
There has to be a more efficient way to do it with either React or pure JavaScript to make it perform better.
Any suggestions?
Nope, you got it.
The best way to edit an item in an array in state is typically to map over it, return the existing item for most indexes, and return your replacement item with the one index you want.
You say this is "very expensive" but a map that does very little processing and returns a new array that is mainly made up of references to objects that already exist is not typically expensive in any way.
If you don't want to do this kind of juggling, immutable data libraries like Immutable.js and others can help.
I tried searching a lot of internet but could not find answer to a simple question. I am very new to mithril (do not know why people chose mithril for project :( ). I want to iterate through a list of strings and use its value in drop down with a checkbox.
const DefaultListView = {
view(ctrl, args) {
const parentCtrl = args.parentCtrl;
const attr = args.attr;
const cssClass = args.cssClass;
const filterOptions = ['Pending', 'Paid', 'Rejected'];
// as of now, all are isMultipleSelection filter
const selectedValue =
parentCtrl.filterModel.getSelectedFilterValue(attr);
function isOptionSelected(value) {
return selectedValue.indexOf(value) > -1;
}
return m('.filter-dialog__default-attr-listing', {
class: cssClass
}, [
m('.attributes', {
onscroll: () => {
m.redraw(true);
}
}, [
filterOptions.list.map(filter => [
m('.dropdown-item', {
onclick() {
// Todo: Add click metrics.
// To be done at time of backend integration.
document.body.click();
}
}, [
m('input.form-check-input', {
type: 'checkbox',
checked: isOptionSelected(filter)
}),
m('.dropdown-text', 'Pending')
])
])
])
]);
}
};
Not sure. How to do it. This is what I have tried so far but no luck. Can someone help me this?
At the beginning of the view function you define an array:
const filterOptions = ['Pending', 'Paid', 'Rejected'];
But later on in the view code where you perform the list iteration, filterOptions is expected to be an object with a list property:
filterOptions.list.map(filter =>
That should be filterOptions.map(filter =>.
There may be other issues with your code but it's impossible to tell without seeing the containing component which passes down the args. You might find it more helpful to ask the Mithril chatroom, where myself and plenty of others are available to discuss & assist with tricky situations.
I'm very new to Redux, and confused as to how to update nested state.
const initialState = {
feature: '',
scenarios: [{
description: '',
steps: []
}]
}
I know that to just push to an array in an immutable way, we could do,
state = {
scenarios: [...state.scenarios, action.payload]
}
And to push into a specific attribute, as this SO answer suggests
How to access array (object) elements in redux state, we could do
state.scenarios[0].description = action.payload
But my question is, how would we access a particular attribute in an object array without mentioning the index? is there a way for us to push it to the last empty element?
All suggestions are welcome to help me understand, thank you in advance :)
Redux helps to decouple your state transformations and the way you render your data.
Modifying your array only happens inside your reducers. To specify which scenario's description you want to modify is easy to achieve by using an identifier. If your scenario has in id, it should be included in your action, e.g.
{
"type": "update_scenario_description",
"payload": {
"scenario": "your-id",
"description": "New content here"
}
}
You can have a reducer per scenario. The higher level reducer for all scenarios can forward the action to the specific reducer based on the scenario id, so that only this scenario will be updated.
In your ui, you can use the array of scenarios and your scenario id to render only the specific one you're currently viewing.
For a more detailed explanation, have a look at the todo example. This is basically the same, as each todo has an id, you have one reducer for all todos and a specific reducer per todo, which is handled by it's id.
In addition to the accepted answer, I'd like to mention something in case someone still wants to "access a particular attribute in an object array without mentioning the index".
'use strict'
const initialState = {
feature: '',
scenarios: [{
description: '',
steps: []
}]
}
let blank = {}
Object.keys(initialState.scenarios[0]).map(scene => {
if (scene === 'steps'){
blank[scene] = [1, 2]
} else {
blank[scene]=initialState.scenarios[0][scene]
}
})
const finalState = {
...initialState,
scenarios: blank
}
console.log(initialState)
console.log(finalState)
However, if scenarios property of initialState instead of being an object inside an array, had it been a simple object like scenarios:{description:'', steps: []}, the solution would have been much simpler:
'use strict'
const initialState = {
feature: '',
scenarios: {
description: '',
steps: []
}
}
const finalState = {
...initialState,
scenarios: {
...initialState.scenarios, steps: [1, 2, 4]
}
}
console.log(initialState)
console.log(finalState)
So I have the following object structure:
const SamplePalette = {
id: 1,
name: "Sample Palette",
description: "this is a short description",
swatches: [
{
val: "#FF6245",
tints: ["#FFE0DB", "#FFA797"],
shades: ["#751408", "#C33F27"]
},
{
val: "#FFFDA4",
tints: ["#FFFFE1"],
shades: ["#CCCB83"]
},
{
val: "#BFE8A3",
tints: ["#E7FFD7"],
shades: ["#95B77E"]
}
]
}
Let's imagine that this object is managed by the state of my app like this:
this.state = {
currentPalette: SamplePalette,
}
My question is how would I go about updating the val property of a given swatch object in the swatches array? Or more generally - how do I only update pieces of this object?
I tried using the update helper as well as to figure out how Object.assign() works, however I've been unsuccessful and frankly can't really grasp the syntax by just looking at examples.
Also, since I'm going to be modifying this object quite a lot, should I look into maybe using Redux?
[EDIT]
I tried #maxim.sh suggestion but with no success:
this.setState(
{ currentPalette: {...this.state.currentPalette,
swatches[0].val: newValue}
})
Consider you have new new_swatches
I think the clearer way is to get array, update it and put back as:
let new_swatches = this.state.currentPalette.swatches;
new_swatches[0].val = newValue;
this.setState(
{ currentPalette:
{ ...this.state.currentPalette, swatches: new_swatches }
});
Also you have : Immutability Helpers or https://github.com/kolodny/immutability-helper
Available Commands
{$push: array} push() all the items in array on the target.
{$unshift: array} unshift() all the items in array on the target.
{$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
{$set: any} replace the target entirely.
{$merge: object} merge the keys of object with the target.
{$apply: function} passes in the current value to the function and updates it with the new returned value.
What's the best/correct way to update a nested array of data in a store using redux?
My store looks like this:
{
items:{
1: {
id: 1,
key: "value",
links: [
{
id: 10001
data: "some more stuff"
},
...
]
},
...
}
}
I have a pair of asynchronous actions that updates the complete items object but I have another pair of actions that I want to update a specific links array.
My reducer currently looks like this but I'm not sure if this is the correct approach:
switch (action.type) {
case RESOURCE_TYPE_LINK_ADD_SUCCESS:
// TODO: check whether the following is acceptable or should we create a new one?
state.items[action.resourceTypeId].isSourceOf.push(action.resourceTypeLink);
return Object.assign({}, state, {
items: state.items,
});
}
Jonny's answer is correct (never mutate the state given to you!) but I wanted to add another point to it. If all your objects have IDs, it's generally a bad idea to keep the state shape nested.
This:
{
items: {
1: {
id: 1,
links: [{
id: 10001
}]
}
}
}
is a shape that is hard to update.
It doesn't have to be this way! You can instead store it like this:
{
items: {
1: {
id: 1,
links: [10001]
}
},
links: {
10001: {
id: 10001
}
}
}
This is much easier for update because there is just one canonical copy of any entity. If you need to let user “edit a link”, there is just one place where it needs to be updated—and it's completely independent of items or anything other referring to links.
To get your API responses into such a shape, you can use normalizr. Once your entities inside the server actions are normalized, you can write a simple reducer that merges them into the current state:
import merge from 'lodash/object/merge';
function entities(state = { items: {}, links: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities);
}
return state;
}
Please see Redux real-world example for a demo of such approach.
React's update() immutability helper is a convenient way to create an updated version of a plain old JavaScript object without mutating it.
You give it the source object to be updated and an object describing paths to the pieces which need to be updated and changes that need to be made.
e.g., if an action had id and link properties and you wanted to push the link to an array of links in an item keyed with the id:
var update = require('react/lib/update')
// ...
return update(state, {
items: {
[action.id]: {
links: {$push: action.link}
}
}
})
(Example uses an ES6 computed property name for action.id)