React Native AsyncStorage not overwriting data - arrays

I am trying to make an app and I have a few objects in an array which I use AsyncStorage for. I need to change one element of an object but if I try changing it, it doesn't keep those changes.
My code:
save = async () => {
this.state.dataSource.notes = this.state.text;
try {
AsyncStorage.clear();
await AsyncStorage.setItem('medicines', JSON.stringify(this.state.dataSource));
} catch (error) {
console.log('error');
}
this.setModalVisible(!this.state.modalVisible);
The this.state.text stores the value of the text input.
The this.state.dataSource stores the array of objects.
Both of these work like they should.
The array looks like this:
Array = [
item = {
id: 'id',
name: 'name',
brand: 'brand',
inname: 'inname',
chosenWeekDay: 'week day',
androidDate: 'date for android',
chosenAndroidTime: 'time for android',
notes: 'notes in a string',
}]

You need to target a specific object in your dataSource array
let { dataSource, text } = this.state;
const yourSpecfiicIndex = 12; // whatever
dataSource[yourSpecfiicIndex].notes = text;
//...
await AsyncStorage.setItem('medicines', JSON.stringify(dataSource));

I found a solutions:
save = async () => {
this.changeNotes(this.state.id, this.state.text);
try {
AsyncStorage.clear();
await AsyncStorage.setItem('medicines', JSON.stringify(this.state.dataSource));
} catch (error) {
console.log('error');
}
this.setModalVisible(!this.state.modalVisible);
}
changeNotes( value, note ) {
for (var i in this.state.dataSource) {
if (this.state.dataSource[i].id == value) {
this.state.dataSource[i].notes = note;
break;
}
}
}

Related

Is there a way to check nested resources in react-admin's matchSuggestion?

The goal is to make an AutocompleteInput check for the filter value not only in the suggestion list directly, but also in the suggestions' references to different resources.
Specifically, say a Quote has a reference to a Contact and to an Address, and the user enters 'abc' in the input. Now, a Quote whose address contains 'abc' should also be displayed in the suggestion list.
The most elegant way would be to use the useGetOne hook like in the following code snippet but you can't call that hook from outside a React component.
const matchAnyNested = (filter, value) => {
if (matchAnyField(filter, value)) return true;
const { data: contact } = useGetOne('contacts', value.contact_id);
if (matchAnyField(filter, contact)) return true;
const { data: account } = useGetOne('accounts', contact.account_id);
if (matchAnyField(filter, account)) return true;
for (let item of value.part_items) {
const part = useGetOne('parts', item.part_id);
if (matchAnyField(filter, part)) return true;
}
return false;
};
[...]
<AutocompleteInput ... matchSuggestion={matchAnyNested} />
Is there a way to fetch records from within the matchSuggestion function or some other way to validate suggestions based on nested records ? Thanks for any help
Because of the React rules of hooks, this doesn't seem to be possible. I ended up implementing this filtering functionality in the backend.
The useGetOne hook, just like other dataProvider hooks, accepts an enabled option. The example from the react-admin documentation shows its usage:
// fetch posts
const { ids, data: posts, loading: isLoading } = useGetList(
'posts',
{ page: 1, perPage: 20 },
{ field: 'name', order: 'ASC' },
{}
);
// then fetch categories for these posts
const { data: categories, loading: isLoadingCategories } = useGetMany(
'categories',
ids.map(id=> posts[id].category_id),
// run only if the first query returns non-empty result
{ enabled: ids.length > 0 }
);
It applies to your case:
const matchAnyNested = (filter, value) => {
const { data: contact } = useGetOne(
'contacts',
value.contact_id,
{ enabled: !matchAnyField(filter, value) }
);
const { data: account } = useGetOne(
'accounts',
contact.account_id,
{ enabled: !matchAnyField(filter, contact) }
);
// ...
};
This won't solve your problem in the loop, though, because of the rules of hooks.
If you do need that loop, your best bet is to use the useDataProvider hook to call the dataProvider directly:
const matchAnyNested = async (filter, value) => {
const dataProvider = useDataProvider();
if (matchAnyField(filter, value)) return true;
const { data: contact } = await dataProvider.getOne('contacts', { id: value.contact_id });
if (matchAnyField(filter, contact)) return true;
const { data: account } = await dataProvider.getOne('accounts', { id: contact.account_id });
if (matchAnyField(filter, account)) return true;
for (let item of value.part_items) {
const part = await dataProvider.getOne('parts', { id: item.part_id });
if (matchAnyField(filter, part)) return true;
}
return false;
};

I get undefined from state

I have problem, in the code below I'm trying to console.log only name of the playLists from state with console.log(this.state.playLists.name); I get undefined.
getPlaylist() {
spotifyApi.getUserPlaylists().then((response) => {
if (response.items) {
const items = response.items;
console.log(items);
const playListsId = items.map((obj) => {
const playList = {
name: obj.name,
id: obj.id,
};
return playList;
});
console.log(playListsId);
this.setState({
playLists: playListsId,
});
}
console.log(this.state.playLists.name);
});
}
Since this.state.playLists is an array you cannot get name on it. you have to get the name of all elements in an array.
Try this in place of console.log(this.state.playLists.name);
this.state.platLists.map(playList => console.log(playList.name));

Why is the original item in my array being overwritten with Object.assign?

I have a unit test that is producing something I didn't expect:
Background: I'm making a simple todo list with Angular/test driven development.
Problem: When I call editTask on an item in the array, it changes the item's value. But, I don't see how it's changed in the original array because the original array is never accessed in the method I'm testing. Please help me connect HOW the original array is being changed? It seems Object.assign is doing this, but why?
describe('editTask', () => {
it('should update the task by id', () => {
const dummyTask1 = { id: 1, name: 'test', status: false };
service.tasks.push(dummyTask1); //refers to TestBed.get(TaskService)
const id = 1;
const values = { name: 'cat', status: false };
service.editTask(id, values);
console.log(service.tasks); // why does this log this object? [Object{id: 1, name: 'cat', status: false}]
expect(service.tasks[0].name).toEqual(values.name); // Test passes
});
});
Here is the method I'm testing:
editTask(id, values) {
const task = this.getTask(id);
if (!task) {
return;
}
Object.assign(task, values); //How does this line change the array?
return task;
}
getTask(id: number) {
return this.tasks.filter(task => task.id === id).pop(); //is this altering the original array somehow?
}
If needed, here's the full Angular service:
export class TaskService {
tasks: any = [];
lastId = 0;
constructor() { }
addTask(task) {
if (!task.id) {
task.id = this.lastId + 1;
}
this.tasks.push(task);
}
editTask(id, values) {
const task = this.getTask(id);
if (!task) {
return;
}
Object.assign(task, values);
return task;
}
deleteTask(id: number) {
this.tasks = this.tasks.filter(task => task.id !== id);
}
toggleStatus(task) {
const updatedTask = this.editTask(task.id, { status: !task.status});
return updatedTask;
}
getTasks() {
return of(this.tasks);
}
getTask(id: number) {
return this.tasks.filter(task => task.id === id).pop();
}
}
Here is the github repo: https://github.com/capozzic1/todo-tdd
The getTask() method is getting a reference to the item in the array using the array filter() method.
It then uses Object.assign() to change the properties of the item. The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
So now the values of the reference in memory of the item is changed. Because it is a reference in memory you will see the original item being changed.

Filter out existing objects in an array of objects

I don't think this is difficult, I just can't figure out the best way to do it. This function is creating an array, from a group of checkboxes. I then want to break up the array and create an array of objects, because each object can have corresponding data. How do I filter out existing rolesInterestedIn.roleType.
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray;
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
}
this.setState({ typeOfWork: newSelectionArray }, function() {
this.state.typeOfWork.map((type) => {
this.setState({
rolesInterestedIn: this.state.rolesInterestedIn.concat([
{
roleType: type,
}
])
}, function() {
console.log(this.state.rolesInterestedIn);
});
})
});
}
UDPATE
rolesInterestedIn: [
{
roleType: '',
experienceYears: ''
}
],
Because each time you do setState you are concatenating the new value to the prev one in rolesInterestedIn array. Add new value only when you are adding new item, otherwise remove the object from both the state variable typeOfWork and rolesInterestedIn.
Try this:
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray, rolesInterestedIn = this.state.rolesInterestedIn.slice(0);
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection);
rolesInterestedIn = rolesInterestedIn.filter(s => s.roleType !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
rolesInterestedIn = newSelectionArray.map((workType) => {
return {
roleType: workType,
experienceYears: '',
}
});
}
this.setState({
typeOfWork: newSelectionArray,
rolesInterestedIn: rolesInterestedIn
});
}
Suggestion: Don't use multiple setState within a function, do all the calculation then use setState once to update all the values in the last.

Firebase database and react native

The below is a sample structure of the database.
root: {
users: {
user_uid_0: {
name: 'AJ',
age: 20,
gender: 'male'
}
}
}
The below is how I am fetching data from Firebase:
getData(myValue) {
// direct ref to a key of 'name' from user_uid_0
const name = firebase.database().ref('/users/user_uid_0/name');
// name will go through a listener
name.on('value', (snapshot) => {
// define myValue to the snapshot
myValue = snapshot.val()
// this.myValue = snapshot.val() tried this...
});
// spit out myValue which we defined
// myValue should be 'AJ'
// but its not... its undefined
return myValue;
// im guessing because myValue is actually not defined...
// then how do I define it with my snapshot.val()?
}
The below is a react native component that is displaying what the function is returning.
// this should return the string 'AJ' from myValue
// this returns nothing
<Text>{this.getData()}</Text>
The below is what I get when I console log.
// undefined is what I get
console.log(this.getData());
So since this wont work, what will? How can I fetch a data through ref and display it? Please help. I have been struggling with this for days.
Try something like this -
getData = async () => {
const name = firebase.database().ref('/users/user_uid_0/name');
var data = await name.once('value')
.then(snapshot => {
console.log("value", snapshot.val());
return snapshot.val();
})
.catch(e => {
console.log("firebase error", e);
return null;
});
return data;
}

Resources