passing reactive data object as argument in function Vue 3 composition API - vuejs3

I am new to Vue and although I could find way around most problems I've encountered, this one has been bugging me for last two days and just cannot find solution. Any help much appreciated, thanks in advance!
I've got following code:
<template>
<header><h1>HomeShop</h1></header>
<main>
<section>
<Item-Card
v-for="(item, index) in items"
:key="index"
:item="item.item"
:check="item.check"
#item-checked="checkClicked(index, item.id, items)"
#item-deleted="deleteClicked(item.id, items)"
/>
<Add-New-Item #item-submited="newItemCard" />
</section>
</main>
<button #click="$log(JSON.stringify(items, null, 1))">Log</button>
</template>
<script setup>
import { reactive, toRefs } from "vue";
import axios from "axios";
import _ from "lodash";
//data + explicit expression
const state = reactive({
items: [],
});
const { items } = toRefs(state);
//fce
const checkClicked = (index, id, items) => {
if (items[index].check === false) {
items[index].check = true;
items[index].checkTime = _.now();
items = _.sortBy(items, ["check", "checkTime"])
console.log(JSON.stringify(items, null, 1));
axios.patch("http://localhost:3000/items/" + id, { check: true });
} else {
items[index].check = false;
delete items[index].checkTime;
axios.patch("http://localhost:3000/items/" + id, { check: false });
}
};
I pass my data object array (items) as argument to checkClicked method. This function changes check status and adds checkTime timestamp based on which I sort the array (using lodash method sortBy). From within the checkClicked method I log the items array with expected correct result:
[
{
"item": "2",
"check": false,
"id": 2
},
{
"item": "3",
"check": false,
"id": 3
},
{
"item": "4",
"check": false,
"id": 4
},
{
"item": "5",
"check": true,
"id": 5,
"checkTime": 1671719753796
},
{
"item": "1",
"check": true,
"id": 1,
"checkTime": 1671719755363
}
]
However, when I log items from outside the method (the custom function $log at template) I get following result:
[
{
"item": "1",
"check": true,
"id": 1,
"checkTime": 1671719755363
},
{
"item": "2",
"check": false,
"id": 2
},
{
"item": "3",
"check": false,
"id": 3
},
{
"item": "4",
"check": false,
"id": 4
},
{
"item": "5",
"check": true,
"id": 5,
"checkTime": 1671719753796
}
]
How do I manipulate the reactive data object array items from inside the function checkClicked? My intention is to sort the data array each time timestamp checkTime is added (that is what the checkClicked function does).
My understanding is that passing the data object array items as argument into function creates separate instance of the array, that is why I am getting two different results while loging the array. However I cannot find solution how to manipulate the real items from inside the function checkClicked.

You are right. You are only changing the parameter variable items in the function. See my other answer for the clarification.
Pay also attention to the following:
When you assign a new value to your reactive proxy object, you can lose the reactivity.
Here it is:
items = _.sortBy(items, ["check", "checkTime"])
I guess, this line could also remove reactivity from the array.
There are ways to fix it, by passing the array through vue reactivity system again.
But, this causes too much work for vue to full recalculate the array items and is not efficient.
My way to provide sorted results is to use a Computed Property.
Like this:
<Item-Card v-for="(item, index) in sortedItems">
and then
const sortedItems = computed(() => {
return _.sortBy(items, ["check", "checkTime"])
})
The advantage of using a computed property is that, it will be recalculated by Vue Reactivity System only when your items array changes.

It looks like you only change the local variable items in the checkClicked() function. I have build a test playground to check it.
You have to change the line items = []; to this.items = []; if you want to change the property, not the parameter variable.
See my second answer for the right sorting solution.
const { ref, reactive, createApp, toRefs } = Vue;
const data = [
{
"item": "1",
"check": true,
"id": 1,
"checkTime": 1671719755363
},
{
"item": "2",
"check": false,
"id": 2
},
{
"item": "3",
"check": false,
"id": 3
}
]
const App = {
methods: {
clear(items) {
items = [];
}
},
setup() {
//data + explicit expression
const state = reactive({
items: data,
});
const { items } = toRefs(state);
return {
items
}
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<div v-for="(item, index) in items"
:key="index"
:item="item.item"
:check="item.check"> #{{index}}: {{item.item}}
</div>
<button #click="clear(items)">clear</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js">
</script>

sending update with solution I got at another forum. I feel dumbed I would not figured that out in two days on my own, whatever. Although advice from Tolbxela can be right track it brings as well more problems so in the end I prefer the more cleaner way:
"I can reference the state declared within . i.e. don’t pass it as a value to the function."
Thats it. Simply not passing the data object array into function, just reference it right away from within the function. So the correct code goes like this:
<template>
<header><h1>HomeShop</h1></header>
<main>
<section>
<Item-Card
v-for="(item, index) in items"
:key="item.id"
:item="item.item"
:check="item.check"
#item-checked="checkClicked(index, item.id)"
#item-deleted="deleteClicked(item.id, items)"
/>
<Add-New-Item #item-submited="newItemCard" />
</section>
</main>
<button #click="$log(JSON.stringify(sortedItems, null, 1))">Log</button>
</template>
<script setup>
import { reactive, toRefs, computed } from "vue";
import axios from "axios";
import _ from "lodash";
//data + explicit expression
const state = reactive({
items: [],
});
const { items } = toRefs(state);
//fce
const checkClicked = (index, id) => {
if (state.items[index].check === false) {
state.items[index].check = true;
state.items[index].checkTime = _.now();
state.items = _.sortBy(state.items, ["check", "checkTime"])

Related

Cypress- How can we list key names under certain key (not values of these keys)

I want to store key names under a certain key. Here is an example:
{"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
}
}}
There is no problem to reach end values like "sun1". I want to reach the key names under "image" as "src", "name" ...; and store them as an array. I don't need their values. How can I do that? I'm parsing response in "then" structure; so this type of answer would be great!
Thanks
The image tag is deeply nested in the outer object.
This is how I would approach it
cy.request(...)
.then(data => {
const imageKeys = Cypress._.keys(data.widget.image)
expect(imageKeys).to.deep.eq(['src', 'name', 'hOffset', 'vOffset', 'alignment'])
})
You can also chain commands,
cy.request(...)
.its('widget.image')
.then(Cypress._.keys)
.should(imageKeys => {
expect(imageKeys).to.deep.eq(['src', 'name', 'hOffset', 'vOffset', 'alignment'])
})
JavaScript has Object.keys(), which will return an array containing the keys in that object.
const myObj = {
foo: true,
bar: false,
baz: 'string'
}
cy.then(() => {
const keys = Object.keys(myObj);
console.log(keys) // ['foo', 'bar', 'baz']
});
If you needed both keys and values, you can use Object.entries().

How to add elements to a svelte writable store vector

I have this store:
export const flights = writable<APIResponse>([])
And I want to add elements at the end of that array. I tried his:
flights.set({ ...flights, input })
But that doesn't add, it overwrites the existing elements, leaving only the one in input. How can I do that?
I am in a .ts. I'm taking over someone else's job who left the company and I'm new to all of this, I still don't have a clear idea of this mix of languages/frameworks.
When I print flights appears empty.
console.warn(flights store: + JSON.stringify(flights))
{}
Some advances. It seems it was not empty. I wasn't printing it the correct way. I can see the elements added if I add them like this:
unconfirmed_flights.update((data) => {
data.push(input))
return data
})
and print the content like this:
unconfirmed_flights .update((data) => {
console.warn(JSON.stringify(data))
return data
})
That prints something like: [{json_object}, {json_object}].
The thing is that in fact I have two stores:
export const flights = writable<APIResponse>([])
export const unconfirmed_flights = writable<APIResponse>([])
The code receives several items that are added to unconfirmed_flights correctly. Then a dialog opens and if the user presses accept I need to copy the items in unconfirmed_flights to flights. I do that like this. First I create an index (id) with the empty array:
flights.update((data) => {
data[id] = []
return data
})
Then I add all the elements in unconfirmed_flights:
unconfirmed_flights.update((uplan) => {
flights.update((data) => {
data[id].push(uplan)
return data
})
return uplan
})
But the result, instead of
{"id": [{json_object}, {json_object}]}
is
{"id": [[{json_object}, {json_object}]]}
With that nested array. However, if I don't do the step of data[id] = [], I get a "Uncaught (in promise) TypeError: data.push is not a function", that I read is because the index does not exist. How can I avoid that nested array?
const flights = writable([])
If you want to add a value to a store from a .js file use .update() where the current value is available
flights.update(value => [...value, newValue])
Inside a .svelte file the store variable can be prefixed with $ to access the value and the new value could be added like this
$flights = [...$flights, newValue]
After three days of unsuccessful attemps to add the content exactly as I needed, I found the help of a JS expert. There it goes the solution.
Having
flights = {"1": { "name": "name1" } }
and
unconfirmed_flights = [ {"2": { "name": "name2" } }, {"3": { "name": "name3" } } ]
The goal was to add the unconfirmed_flights to flights:
flights = {"1": { "name": "name1" },
"2": { "name": "name2" },
"3": { "name": "name3" } }
And that was done like this:
flights.update((plan) => {
const uplan = get(unconfirmed_flights)
uplan.forEach((uplanItem) => {
plan = { ...plan, ...uplanItem }
})
return plan
})
being
export type APIResponse = { [key: string]: any }
export const flights = writable<APIResponse>([])
export const unconfirmed_flights = writable<APIResponse[]>([])

How to hide selected option in Autocomplete using reactjs

I want to hide the selected option in the Dropdown, the option should not appear in the next dropdown. For an example, there are 2 dropdowns, in the first dropdown - i have selected "Hockey" then "hockey" should not be shown in the second dropdown, It should show only "Baseball and badminton".
My JSON data will be appearing in this way:
"details": [
{ "id": "12wer1", "name": "ABC", "age": 15, "game": "badminton" },
{ "id": "78hbg5", "name": "FRE", "age": 21, "game": "Hockey" }
]
Here is the sample Code:
let games = [{ game: "Baseball"}, { game: "Hockey"}, { game: "badminton" }];
class Field extends React.Component {
constructor(props) {
super(props);
this.state = {
details: [{id: '', name: '', age: '', game: ''}]
}
}
...
...
render() {
return (
...
...
{this.state.details.map((y) => (
<Autocomplete
style={{ witdth: 200 }}
options={games}
getOptionLabel={(option) => option.game}
onChange={(value) =>this.onGameChange(value, y.id)}
value={games.filter(i=> i.game=== y.game)}
renderInput={(params) =>
<TextField {...params} label="Games" margin="normal" />
}
/>))}
...
...
)
}
}
onGameChange = (e, id)=> {
let games = this.state.details;
let data = games.find(i => i.id === id);
if (data) {
data.game = value.game;
}
this.setState({ details: games });
}
I have no idea, how to hide the selected option, can anyone help me in this query?
Thanks! in advance.
A possible solution would be to
create an array and store the values in an array when the user selects autocomplete
while passing options, filter the values that have been passed to other autocompletes.
const ary = [111,222,333];
let obj = [{id: 111},{id: 222}];
const i = 1; // this is your index in loop
const ary2 = ary.slice()
ary2.splice(i,1);
console.log(obj.filter((o) => !ary.includes(o.name))); // this should be given to our options list in autocomplete
you can hide this is in CSS easily no need to do anything in ReactJS
autocomplete renders as an unordered list so something like this
.panel > ul > li:first-child {
display:none;
}

React/redux : this.props returns undefined value + array

I am using React/redux to build my app.
Here is my data (json)
{
"categories": [
{
"id": 1,
"name": "category 1",
"slug": "category-1"
"content": [
{
"id": 1,
"title": "title 1 "
},
{
"id": 2,
"title": "title 2 "
}
]
}
]
}
what I want to do :
select a category
display the content of the category selected
my store is like that :
categoryItems: [], // list of categories
categorySelected: []
data is saved correctly on my state when I select a category
on Category component I implement the function selectCategory which calls the action SELECT_CATEGORY and dispatch data to reducer. so far so good!
getCategoryList() {
return this.props.categories.map((category) => {
return (<li key={category.id}>
<Link to={`/category/${category.slug}`} onClick={() =>this.props.selectCategory(category.slug)} >{category.name} </Link>)});}
and on CategoryDetail, when I console.log(this.props.categorySelected.name) I got:
undefined
name
here is the problem because I can not read this.props.categorySelected.name
How to fix this and read proprely this props?
You have to give the complete object to your selectCategory() function.
Like this:
getCategoryList() {
return this.props.categories.map((category) => {
return (<li key={category.id}>
<Link to={`/category/${category.slug}`} onClick={() =>this.props.selectCategory(category)} >{category.name} </Link>)});
}

Firebase Functions Database 'child_added' Only Returning 1st Child (out of many children)

I'm trying to figure out how to return
{"length": "2","height": "4"},{"length": "1.5","height": "6"},{"length": "3","height": "5.5"},{"length": "2","height": "3.2"} from the following setup in my RTDB. What am I forgetting to include?
{
"widgets": {
"widget01": {
"length": "2",
"height": "4"
},
"widget02": {
"length": "1.5",
"height": "6"
},
"widget03": {
"length": "3",
"height": "5.5"
},
"widget04": {
"length": "7",
"height": "3.2"
}
}
}
My function is...
exports.widgets = functions.database.ref().onUpdate(event => {
admin.database().ref('/widget').once('child_added', snapshot =>{
let data = snapshot.val()
console.log(data)
return ({data})
});
});
But this is only returning {"length": "2","height": "4"} and not every child within "widget[i]". I thought...
...child_added is triggered once for each existing child...
PS. Instead of child_added I did try to use value. However I couldn't figure out how to reference "widget[i]" within .child() since it is dynamic.
exports.widgets = functions.database.ref().onUpdate(event => {
admin.database().ref('/widgets').child(<<how to structure?>>).once('value', snapshot =>{
let data = snapshot.val()
console.log(data)
return ({data})
});
});
Since you're calling once('child_added', your callback will only be triggered once: for the first child in the location you attach the listener to.
If you need all children in Cloud Functions, you should indeed use once('value', which gives all matching child nodes in the snapshot at once. You then loop over those children with snapshot.forEach():
exports.widgets = functions.database.ref().onUpdate(event => {
admin.database().ref('/widgets').once('value', snapshot =>{
snapshot.forEach((child) => {
let data = child.val()
console.log(data)
//return ({data})
});
});
});
I'm not sure what you're trying to return though. Since there can be multiple children, what child do you want to return?

Resources