read from database and change state within the same asynchronous call with the database result [duplicate] - react-native

I am recently learning the map and filter method in react native and I have a question. After finding a particular row of my array (with filter), how do I set only a particular field of that specific row?
I have
this.state = {
post: [{ id: "0", author: "Duffy Duck", delay: "1", picture: "" }]
}
putpicture(id) {
const picture_new = "/gggg(yyb45789986..."
const data = this.state.post
.filter((item) => item.author == id)
// my error is here. How can i put picture_new const, inside post.picture?
.map((pictures) => this.setState({ post.picture: picture_new }))
}
Now i want setState inside map and filter for every post.
i want this output:
id:"0", author:"Duffy Duck",delay:"1", picture:"/gggg(yyb45789986..."
How can i do?

It seems to me that you're only looking to change one particular entry of your array.
It also seems like you meant to search by id and not author.
If that's the case, you'll want to use findIndex to find the correct index.
Make a shallow copy of the array so as not to modify the original.
Then assign a modified copy of that object to that index, again so as not to modify the original.
Then assign the new array to the state.
Since the updated state relies on the previous state, you should house all of this within a setState callback function. This is because state updates may be asynchronous, as outlined here: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous. In other words, this ensures you get the most up to date version of your state at the time of applying the change.
putpicture(id) {
const picture_new = '/gggg(yyb45789986...';
this.setState(({ post }) => {
// find index of item to edit
const index = post.findIndex((item) => item.id === id);
if (index === -1) return;
// create shallow copy of array
const newPost = [...post];
// replace old object with new object
newPost[index] = { ...post[index], picture: picture_new };
return { post: newPost };
});
}
Stackblitz: https://stackblitz.com/edit/react-ts-ht8hx9?file=App.tsx
If I've misunderstood your intentions, please reword your question to be more clear. You use the term "row of my array", but this a 1-dimensional array, there are no rows. In the question you also mention modifying "every post", but in a comment you mention modifying only "a post".

This doesn't 100% make sense to me - are you sure you want to set all pictures on posts by an author at once? Is the post array meant to hold multiple posts? It looks like that's the goal based on the code, so I'll answer that as is.
EDIT: changed to reflect one post at a time.
You're close to a solution - the things you're missing are
You can use .find instead of .filter since you only want one post
Since you can use .find, which returns a single item, you don't need to .map.
putpicture(id) {
const picture_new = "/gggg(yyb45789986...";
const modifiedPost = this.state.post.find((item) => item.id === id);
modifiedPost.picture = picture_new;
// now that you have the modified post, put it into state
// first, get all the other (unmodified) posts
const otherPosts = this.state.post.filter((item) => item.id !== id);
// add the new post to the array and set state
this.setState({ post: [...otherPosts, modifiedPost] });
}
As an aside, if you're going to be doing this a lot, I would recommend making your state an object with IDs as the keys, instead of an array. .find and .filter loop over each array member and can do a lot of unnecessary work. If you set up your state like this:
this.state = {
post: {
0: { id: "0", author: "Duffy Duck", delay: "1", picture: "" },
}
};
then you can modify it much more easily:
putpicture(id) {
const picture_new = "/gggg(yyb45789986...";
const newPosts = {
...this.state.post,
[id]: {
...this.state.post[id],
picture: new_picture,
}
};
this.setState({ post: newPosts });
}
This is much faster when dealing with large arrays.

In case you can have more than one post in array and anything apart of post in you state:
putpicture(id) {
const picture_new = "/gggg(yyb45789986...";
// create new array by reducing source array
const newPost = this.state.post.reduce((res, it) => {
// update only item(s) matching the condition
res.push(it.author === id ? {...it, picture: picture_new} : {...it});
return res;
}, []);
// use destructuring and previous state to update the state
this.setState(prev => {...prev, post: newPost});
}

Related

Vue Reading in parallel in a loop

I have an Set array which contains multiple Ids.I would like to loop through the Set and make the api calls in parallel for each id and get back the user object, add it to a map.How can i achieve it.
The value is Set
userIds :Set[2]
0:"1"
1:"2"
data() {
return {
userIds: new Set(),
};
},
const res = getUsers(userId)
hope it will resolve your issues. i did not test, just writing code here directly.
// set requests
let allRequests = []
//you can use other loop based on your decession
this.userIds.forEach(id => { allRequests.push(axios.get(`your_url\${id}`)) })
// you can use await it is based on you requirement
axios.all(allRequests).then(axios.spread((...responses) => {
//make your map here using responses
})).catch(errors => {
// react on errors.
})
you can check this reference

Computed Property causes array and object mutation

I am trying to pass data to a v-select dropdown.
This of course works:
computed: {
itemDropdown() {
const menuItems = {
id: "1",
name: "Joe"
}
return menuItems;
}
}
But when I try:
computed: {
itemDropdown() {
const newArray = [...this.data.originalItems];
newArray.map(item => {
item.name = "myCoolNewName";
});
return newArray;
}
}
It mutates the original array.
I have also tried copying the object:
computed: {
itemDropdown() {
const newObj = { ...this.data };
newObj.items.map(item => {
item.name = "myCoolNewName";
});
return newObj;
}
}
Not sure what I’m missing, but wondering if there is a work around. Thanks for any help :slight_smile:
You are using the map array method wrong.
The first thing you need to know, is that the map method returns a new array, so you have to either return the result of your map function or save it in a variable, otherwise you will just be looping through your array without ever saving it anywhere.
Another thing is about how you use the map method.
Here I have made an example of how it should work with your code:
computed: {
itemDropdown() {
return this.data.originalItems.map(item => {
return {
name: "myCoolNewName"
}
});
}
}
The big difference you should notice, is that inside the map function, we have to return what we want each object to look like, after we have gone through it. We want it to give us the object back, but make some changes to it, so we have to actually return an object and change what we want in that.
What you were doing before, was refering to the item in the old array, and assigning it a new value, instead of returning a new object with your changes.
You can read about the array.map method here
Hope that makes sense :)

Always first filtered selected on Quasar Select

I am using Quasar Framework and using the Q-Select with filter.
I would like to the first filtered option always be already marked, because then if I hit enter, the first will selected.
After some research I found out how to achieve this in a generic way.
The second parameter on the function update received at filterFn is the instance of QSelect itself.
Hence, we can use
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
To keep the focus on the first filtered element, regardless of multiselect or not.
The final code is something like
filterFn(val, update) {
update(
() => {
const needle = val.toLocaleLowerCase();
this.selectOptions = this.qSelectOptions.filter(v => v.toLocaleLowerCase().indexOf(needle) > -1);
},
ref => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
});
}
There is one option to achieve this is set model value in the filter method if filtered options length is >0.
filterFn (val, update, abort) {
update(() => {
const needle = val.toLowerCase()
this.options = stringOptions.filter(v => v.toLowerCase().indexOf(needle) > -1)
if(this.options.length>0 && this.model!=''){
this.model = this.options[0];
}
})
}
Codepen - https://codepen.io/Pratik__007/pen/QWjYoNo

Lists and Components not updating after data change - (VueJS + VueX)

A question about best practice (or even a go-to practice)
I have a list (ex. To-do list). My actual approach is:
On my parent component, I populate my 'store.todos' array. Using a
getter, I get all the To-do's and iterate on a list using a v-for
loop.
Every item is a Component, and I send the to-do item as a prop.
Inside this component, I have logic to update the "done" flag. And this element display a checkbox based on the "state" of the flag. When it does that, it do an action to the db and updates the store state.
Should I instead:
Have each list-item to have a getter, and only send the ID down the child-component?
Everything works fine, but if I add a new item to the to-do list, this item is not updated when I mark it as completed. I wonder if this issue is because I use a prop and not a getter inside the child component
Code:
store:
const state = {
tasks: []
}
const mutations = {
CLEAR_TASKS (state) {
state.tasks = [];
},
SET_TASKS (state, tasks) {
state.tasks = tasks;
},
ADD_TASK (state, payload) {
// if the payload has an index, it replaces that object, if not, pushes a new task to the array
if(payload.index){
state.currentSpaceTasks[payload.index] = payload.task;
// (1) Without this two lines, the item doesn't update
state.tasks.push('');
state.tasks.pop();
}
else{
state.tasks.push(payload.task);
}
},
SET_TASK_COMPLETION (state, task){
let index = state.tasks.findIndex(obj => obj.id == task.id);
state.tasks[index].completed_at = task.completed_at;
}
}
const getters = {
(...)
getTasks: (state) => (parentId) => {
if (parentId) {
return state.tasks.filter(task => task.parent_id == parentId );
} else {
return state.tasks.filter(task => !task.parent_id );
}
}
(...)
}
const actions = {
(...)
/*
* Add a new Task
* 1st commit add a Temp Task, second updates the first one with real information (Optimistic UI - or a wannabe version of it)
*/
addTask({ commit, state }, task ) {
commit('ADD_TASK',{
task
});
let iNewTask = state.currentSpaceTasks.length - 1;
axios.post('/spaces/'+state.route.params.spaceId+'/tasks',task).then(
response => {
let newTask = response.data;
commit('ADD_TASK',{
task: newTask,
index: iNewTask
});
},
error => {
alert(error.response.data);
});
},
markTaskCompleted({ commit, dispatch, state }, task ){
console.log(task.completed_at);
commit('SET_TASK_COMPLETION', task);
dispatch('updateTask', { id: task.id, field: 'completed', value: task.completed_at } ).then(
response => {
commit('SET_TASK_COMPLETION', response.data);
},
error => {
task.completed_at = !task.completed_at;
commit('SET_TASK_COMPLETION', task);
});
},
updateTask({ commit, state }, data ) {
return new Promise((resolve, reject) => {
axios.patch('/spaces/'+state.route.params.spaceId+'/tasks/'+ data.id, data).then(
response => {
resolve(response.data);
},
error => {
reject(error);
});
})
}
}
And basically this is my Parent and Child Components:
Task List component (it loads the tasks from the Getters)
(...)
<task :task = 'item' v-for = "(item, index) in tasks(parentId)" :key = 'item.id"></task>
(...)
The task component display a "checkbox"(using Fontawesome). And changes between checked/unchecked depending on the completed_at being set/true.
This procedure works fine:
Access Task list
Mark one existing item as done - checkbox is checked
This procedure fails
Add a new task (It fires the add task, which firstly adds a 'temporary' item, and after the return of the ajax, updates it with real information (id, etc..). While it doesn't have the id, the task displays a loading instead of the checkbox, and after it updates it shows the checkbox - this works!
Check the newly added task - it does send the request, it updates the item and DB. But checkbox is not updated :(
After digging between Vue.js docs I could fix it.
Vue.js and Vuex does not extend reactivity to properties that were not on the original object.
To add new items in an array for example, you have to do this:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
More info here:
https://v2.vuejs.org/v2/guide/reactivity.html
and here: https://v2.vuejs.org/v2/guide/list.html#Caveats
At first it only solved part of the issue. I do not need the "hack" used after pushing an item into the array (push and pop an empty object to force the list to reload)
But having this in mind now, I checked the object returned by the server, and although on the getTasks, the list has all the fields, including the completed_at, after saving a new item, it was only returning the fields that were set (completed_at is null when created). That means that Vue.js was not tracking this property.
I added the property to be returned by the server side (Laravel, btw), and now everything works fine!
If anybody has a point about my code other than this, feel free to add :)
Thanks guys

Using map to reduce in Gun

I am new to Gun. I have existing code that very effectively reduces an array of objects based on a pattern. I am thinking I should tweak this to run in the context of Gun's .map and return undefined for non-matches. I think I will also have to provide two arguments, one of which is the where clause and the other the properties I want shown on returned objects. I also presume that if I use .on future matches will automagically get spit out! Am I on the right path?
const match = (object,key,value) => {
const type = typeof(value);
if(value && type==="object") {
return Object.keys(value).every(childkey =>
match(object[key],childkey,value[childkey]));
if(type==="function") return value(object[key]);
return object[key]===value;
}
const reduce = (objects,where) => {
const keys = Object.keys(where);
return objects.reduce((accumulator,current) => {
if(keys.every(key => match(current,key,where[key]))) {
accumulator.push(current);
}
return accumulator;
},[]);
}
let rows = reduce([{name: "Joe",address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16},
{name: "Joe",address:{city: "New York"},age:20}],
{name: () => true,
address: {city: "Seattle"},
age: (age) => age > 10});
// results in
[{name: "Joe",address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16}]
Further exploration of this resulted in the code below, which is stylistically different, but conforms to the immediate responsive nature of Gun. However, it is unclear how to deal with nested objects. The code below only works for primitives.
const match = (object,key,value) => {
const type = typeof(value);
if(!object || typeof(object)!=="object") return false;
if(value && type==="object") {
const child = gun.get(object[key]["#"]);
for(let key in value) {
const value = {};
child.get(key).val(v => value[key] = v,{wait:0});
if(!match(value,key,value[key])) return;
}
}
if(type==="function") return value(object[key]);
return object[key]===value;
}
const gun = Gun(["http://localhost:8080/gun"]),
users = [{name: "Joe",address:{city: "Seattle"},age:25},
{address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16},
{name: "Joe",address:{city: "New York"},age:20}];
//gun.get("users").map().put(null);
for(let user of users) {
const object = gun.get(user.name).put(user);
gun.get("users").set(object);
}
gun.get("users").map(user => {
const pattern = {name: (value) => value!=null, age: (age) => age > 20}; //, address: {city: "Seattle"}
for(let key in pattern) {
if(!match(user,key,pattern[key])) return;
}
return user;
}).on(data => console.log(data));
Yes. GUN's .map method does more than what it seems.
Say we have var users = gun.get('users'). We can do:
users.map() with no callback acts like a forEach because the default callback is to return the data as-is.
users.map(user => user.age * 2) with a callback, it lets you transform the data like you would expect from a map, except where:
users.map(function(){ return }) if you return undefined, it will filter out that record.
WARNING: As of the current time, .map(transform) function is currently experimental and my have bugs with it. Please try it and report any you find.
Now we can combine it with some other methods, to get some cool behavior:
users.map().on(cb) will get current and future users as they are added to the table, and gets notified for updates on each of those users.
users.map().val(cb) will get current and future users as they are added to the table, but only gets each one once.
users.val().map().on(cb) gets only the current users (not future), but gets the updates to those users.
users.val().map().val(cb) gets only the current users (not future), and only gets them once.
So yes, you are on the right track. For instance, I have a test in gun core that does this:
list.map(user => user.age === 27? user.name + "thezombie" : u).on(function(data){
// verify
});
list.set({name: 'alice', age: 27});
list.set({name: 'bob', age: 27});
list.set({name: 'carl', age: 29});
list.set({name: 'dave', age: 25});
This creates a live map that filters the results and locally (view only) transforms the data.
In the future, this is how the SQL and MongoDB Mango query extensions will work for gun.
Note: GUN only loads the property you request on an object/node, so it is bandwidth efficient. If we do users.map().get('age') it will only load the age value on every user, nothing else.
So internally, you can do some efficient checks, and if all your conditionals match, only /then/ load the entire object. Additionally, there are two other options: (1) you can use an in-memory version of gun to create server-side request-response patterns, so you can have server-side filtering/querying that is efficient. (2) if you become an adapter developer and learn the simple wire spec and then write your own custom query language extensions!
Anything else? Hit me up! More than happy to answer.
Edit: My reply in the comments, comments apparently can't have code. Here is pseudo-code of how to "build up" more complex queries, which will be similar to how SQL/Mango query extensions will work:
mutli-value & nested value matching can be "built up" from this as the base, but yes, you are right, until we have SQL/Mango query examples, there isn't a simple/immediate "out of the box" example. This is pseudo code, but should get the idea across:
```
Gun.chain.match = function(query, cb){
var gun = this;
var fields = Object.keys(query);
var check = {};
fields.forEach(function(field){
check[field] = true;
gun.get(field).val(function(val){
if(val !== query[field]){ return }
check[field] = false;
//all checks done?
cb(results)
});
});
return gun;
}
```
Solution, the trick is to use map and not val:
Gun.chain.match = function(pattern,cb) {
let node = this,
passed = true,
keys = Object.keys(pattern);
keys.every(key => {
const test = pattern[key],
type = typeof(test);
if(test && type==="object") {
node.get(key).match(test);
} else if(type==="function") {
node.get(key).map(value => {
if(test(value[key])) {
return value;
} else {
passed = false;
}
});
} else {
node.get(key).map(value => {
if(value[key]===test) {
return value;
} else {
passed = false;
}
});
}
return passed;
});
if(passed && cb) this.val(value => cb(value))
return this;
}
const gun = new Gun();
gun.get("Joe").put({name:"Joe",address:{city:"Seattle"},age:20});
gun.get("Joe").match({age: value => value > 15,address:{ city: "Seattle"}},value => console.log("cb1",value));