Updating complicated Object with useState - javascript

I'm trying to do a set State function and change only one value.
This is the object and for example, I want to change book1 in index [0] (so name1) from true to false.
I can't understand how to do it
everything that I'm trying simply overwrites part of the object
{
book1: [
{ name: 1, selected: true },
{ name: 2, selected: false },
{ name: 3, selected: false },
{ name: 4, selected: false },
{ name: 5, selected: true },
],
book2: [
{ name: 1, selected: false },
{ name: 2, selected: false },
{ name: 3, selected: true },
{ name: 4, selected: false },
{ name: 5, selected: true },
],
}

Because the object is in state, you can't modify it directly. Instead, you have to create a copy of the object and any inner objects (including arrays) that you change. See comments:
// Use the callback form because you're updating state based on existing state
setTheObject(original => {
// Shallow copy the object and book1
const update = {...original, book1: [...original.book1]};
// Replace the object at index 0 with a new updated object
update.book1[0] = {...update.book1[0], selected: false};
// return the update
return update;
});
It is technically possible to do this in one nested operation, but it's much harder to read and debug:
// Use the callback form because you're updating state based on existing state
setTheObject(original => ({
// Shallow copy the object
...original,
// Shallow copy `book1` while updating index 0 with a copy of the object with the updated property
book1: Object.assign([], original.book1, {0: {...original.book1[0], selected: false}}),
}));
I don't recommend it. :-D

const [books, setBooks] = useState({
book1: [
{ name: 1, selected: true },
{ name: 2, selected: false },
{ name: 3, selected: false },
{ name: 4, selected: false },
{ name: 5, selected: true }
],
book2: [
{ name: 1, selected: false },
{ name: 2, selected: false },
{ name: 3, selected: true },
{ name: 4, selected: false },
{ name: 5, selected: true }
]
});
Assume you hold the books state like this. Now update the books state like this.
setBooks({
...books,
book1: books.book1.map((book) =>
book.name === 1 ? { ...book, selected: !book.selected } : book
)
});
In order to check the effect after state is changed, you can add dependancy to useEffect hook and as soon the books are changed, you will see the updated state in useEffect.
useEffect(() => {
console.log(books);
}, [books]);

Related

Filter based on a String comparison Function

A am trying to filter the name property in each object (that is inside an array) based on whether the comparison function comes back true or not. However I am not sure if I am going about it correctly. I am using the localcomapre function. Below are my code snippets.
Below is my comparison hook.
export default function useLocalCompare(s1, s2) {
s1.trim();
s2.trim();
if (s1.localeCompare(s2, undefined, { sensitivity: "base" }) === 0) {
console.log("Both supplied parameters are the same");
return "True";
} else {
return "False";
}
}
And The filter function looks like
const filtteredarray = flatarrayofvalues.filter(
useLocalCompare(placeholder.name, genre)
);
Am I doing this directly. I only want it to filter out the object where the name property matches the genre value after both have gone through my useLocalCompare function.
Value of filtteredarray below
[{ id: 28, name: 'Action' },
{ id: 12, name: 'Adventure' },
{ id: 16, name: 'Animation' },
{ id: 35, name: 'Comedy' },
{ id: 80, name: 'Crime' },
{ id: 99, name: 'Documentary' },
{ id: 18, name: 'Drama' },
{ id: 10751, name: 'Family' },
{ id: 14, name: 'Fantasy' },
{ id: 36, name: 'History' },
{ id: 27, name: 'Horror' },
{ id: 10402, name: 'Music' },
{ id: 9648, name: 'Mystery' },
{ id: 10749, name: 'Romance' },
{ id: 878, name: 'Science Fiction' },
{ id: 10770, name: 'TV Movie' },
{ id: 53, name: 'Thriller' },
{ id: 10752, name: 'War' },
{ id: 37, name: 'Western' },
{ name: 'Trending' },
{ name: 'Top Rated' }
]
As already mentioned, you are returning strings with "True" and "False", which are not boolean values and so they do not evaluate properly in the filter().
Also, because your useLocalCompare function is just using an if statement evaluation, you can just return the if statement (as it evaluates to true or false itself).
So something like this should work:
export default function useLocalCompare(s1, s2) {
// This evaluates to true or false, so we can return it
return (s1.trim().localeCompare(s2.trim(), undefined, { sensitivity: "base" }) === 0);
}
It should also be noted that the filter method needs to use at least 1 parameter, being the current item from the array it is iterating through for evaluation. So your filter code needs to be slightly altered to use that parameter so it can actually loop through the flattarrayofvalues array and filter the results.
const filtteredarray = flatarrayofvalues.filter(placeholder =>
useLocalCompare(placeholder.name, genre)
);
However, because this is just an if statement evaluation, you could just incorporate the whole thing into the filter directly.
const filtteredarray = flatarrayofvalues.filter(placeholder =>
(placeholder.name.trim().localeCompare(genre.trim(), undefined, { sensitivity: "base" }) === 0)
);

Dealing with looping through nested arrays

I have data array, which has nested arrays inside (level1arr, leve21arr ...)
const data = [
{
level1arr: [
{
level2arr: [{ id: 1, isValid: true }, { id: 2, isValid: true }, { id: 3, isValid: true }],
},
{
level2arr: [{ id: 4, isValid: true }, { id: 5, isValid: true }, { id: 6, isValid: true }],
},
],
},
{
level1arr: [
{
level2arr: [{ id: 7, isValid: true }, { id: 8, isValid: true }, { id: 9, isValid: true }],
},
{
level2arr: [{ id: 10, isValid: true }, { id: 11, isValid: true }, { id: 12, isValid: true }],
},
],
},
];
I also have another array:
const invalidIds = [2,5]
I want to find elements with apecyfic id and change isValid property to false.
Is it better way than iteratinf over multiple nested arrays, like that:
data.forEach(lvl1 => {
lvl1.level1arr.forEach(lvl2 => {
lvl2.level2arr.forEach(element => {
// further nesting
});
});
})
Such iterating over multiple arrays is not good for performance. What is the best way to handle such case with nested arrays?
If it were nested arrays, you could use Array.prototype.flat(). However, you have a mix of nested objects and arrays. You will have to write a custom "flattener" for this data structure. Check this answer for details: how to convert this nested object into a flat object?
You can use recursion until you reach the level you need. Here's one way to do it.
const data = [{
level1arr: [{
level2arr: [{
id: 1,
isValid: true
}, {
id: 2,
isValid: true
}, {
id: 3,
isValid: true
}],
},
{
level2arr: [{
id: 4,
isValid: true
}, {
id: 5,
isValid: true
}, {
id: 6,
isValid: true
}],
},
],
},
{
level1arr: [{
level2arr: [{
id: 7,
isValid: true
}, {
id: 8,
isValid: true
}, {
id: 9,
isValid: true
}],
},
{
level2arr: [{
id: 10,
isValid: true
}, {
id: 11,
isValid: true
}, {
id: 12,
isValid: true
}],
},
],
},
];
const invalidIds =[2,5]
const findId = (object, key, value) => {
if (Array.isArray(object)) {
for (const obj of object) {
findId(obj, key, value);
}
} else {
if (object.hasOwnProperty(key) && object[key] === value) {
object.isValid = false;
return object
}
for (const k of Object.keys(object)) {
if (typeof object[k] === "object") {
findId(object[k], key, value);
}
}
}
}
invalidIds.forEach(id => findId(data, "id", id))
console.log(data)

extract id from array using map with condition javascript

There is an array of objects
const groups = [
{ id: 0, name: "All", selected: false },
{ id: -1, name: "All", selected: true },
{ id: 1, name: "Group1", selected: false },
{ id: 2, name: "Group2", selected: false },
{ id: 3, name: "Group3", selected: false },
{ id: 4, name: "Group4", selected: true }
];
I want to extract ids from this object with map
groups.map(group => group.id > 0 && group.selected ? group.id:null)
but the result will be
[null,null,4,null...]
actually it should be [4]
I know I can use another function like forEach and push or map and filter but I would solve it with one iteration with map or something else.
Filter the object/s under your criteria and then extract the id/s with a map
const groups = [{
id: 0,
name: "All",
selected: false
},
{
id: -1,
name: "All",
selected: true
},
{
id: 1,
name: "Group1",
selected: false
},
{
id: 2,
name: "Group2",
selected: false
},
{
id: 3,
name: "Group3",
selected: false
},
{
id: 4,
name: "Group4",
selected: true
}
];
const result = groups.filter(x => x.id > 0 && x.selected).map(x => x.id)
console.log(result)
you can use a transducer in this case, so that you will not iterate through the array 2 times.
const groups = [
{ id: 0, name: "All", selected: false },
{ id: -1, name: "All", selected: true },
{ id: 1, name: "Group1", selected: false },
{ id: 2, name: "Group2", selected: false },
{ id: 3, name: "Group3", selected: false },
{ id: 4, name: "Group4", selected: true }
];
const filteredIds = groups.reduce(
(ids, { id, selected }) => (
id > 0 && selected ? [...ids, id] : ids
), []
);
console.log(filteredIds);
The map() method creates a new array with the results of calling a function for every array element and extraction is not possible with this. Either use map() and then discard the array items or use filter().
Better approach would be using filter(). The filter() method creates an array filled with all array elements that pass a test (provided as a function).
let result = groups.filter(x => x.id > 0 && x.selected).map(x => x.id)
You can easily do this in one iteration with transducers.
const getPositiveSelectedIDs = pipe([
filter(and([
gt(get('id'), 0),
get('selected'),
])),
map(get('id')),
])
transform(getPositiveSelectedIDs, [])(groups) // => [4]
In this example, getPositiveSelectedIDs is a transducer we declared using functions pipe, map, and filter. The predicate passed to filter uses functions and, gt, and get to say
only let groups through who have positive ids and who have been selected
then, without creating any intermediate arrays, we get the ids of each group with map and get. getPositiveSelectedIDs behaves as a transducer when we use it in transform. The flow of data starts when we call the transformation transform(getPositiveSelectedIDs, []) with groups.
More on transducers

ReactJS - Swap out all the values in an array of objects in state with another array

I am trying to swap out all the values of an array of objects in state with a whole new array of objects. However, nothing seems to be working. I've tried the following:
const list1 = [
{ id: 1, name: 'item1' },
{ id: 2, name: 'item1' },
{ id: 3, name: 'item1' },
{ id: 4, name: 'item1' },
]
const list2 = [
{ id: 1, name: 'newItem1' },
{ id: 2, name: 'newItem2' },
{ id: 3, name: 'newItem3' },
{ id: 4, name: 'newItem4' },
]
class FindTab extends Component {
state = {
status: 'loading',
location: null,
view: this.props.view,
map: this.props.map,
locationValues: list1,
}
}
this.setState(prevState => ({
locationValues: [ ...prevState.locationValues, list2 ],
}))
or just simpler:
this.setState(locationValues: list2)
Neither seem to work. Is there any guidance as to how one should replace an array of objects with another array for a state property?
You could spread the array in a new one like:
const locationValues = [ ...state.locationValues, ...list2 ]
this.setState({ locationValues })

Chai-related error message: "AssertionError: expected undefined to deeply equal"

I wrote a function that when given a list of objects and an
id, returns the same list, but with the corresponding object marked active
(all other objects should not be active).
const list = [
{ id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: true },
{ id: 4, active: false }
];
function markActive(list, value) {
list.forEach((id) => {
if (id.active = (id.id === value)) {
return true;
} else {
return false;
}
});
}
markActive(list, 2);
console.log(list)
Returns:
[ { id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: false },
{ id: 4, active: true } ]
It's working like a charm, except when I run "npm run [filename]" I get an error message:
Running Tests for [filename].
------------
[ { id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: false },
{ id: 4, active: true } ]
markActive
1) Case 1 (Given Sample)
2) Case 2 (String IDs)
0 passing (16ms)
2 failing
1) markActive Case 1 (Given Sample):
AssertionError: expected undefined to deeply equal [ { id: 1,
active: false },
{ id: 2, active: true },
{ id: 3, active: false },
{ id: 4, active: false } ]
at Function.assert.deepEqual
(node_modules/chai/lib/chai/interface/assert.js:216:32)
at Context.it (tests/test_02.js:23:12)
2) markActive Case 2 (String IDs):
AssertionError: expected undefined to deeply equal [ { id: '1',
active: false },
{ id: '2', active: true },
{ id: '3', active: false },
{ id: '4', active: false } ]
at Function.assert.deepEqual
(node_modules/chai/lib/chai/interface/assert.js:216:32)
at Context.it (tests/test_02.js:40:12)
Any idea what I'm doing wrong? Here's the code that sets up the tests:
const chai = require("chai");
const sinon = require("sinon");
const assert = chai.assert;
const markActive = require("../answers/02.js");
describe("markActive", () => {
it("Case 1 (Given Sample)", () => {
var list = [
{ id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: true },
{ id: 4, active: false }
];
var newList = markActive(list, 2);
var targetList = [
{ id: 1, active: false },
{ id: 2, active: true },
{ id: 3, active: false },
{ id: 4, active: false }
];
assert.deepEqual(newList, targetList);
});
it("Case 2 (String IDs)", () => {
var list = [
{ id: "1", active: false },
{ id: "2", active: false },
{ id: "3", active: true },
{ id: "4", active: false }
];
var newList = markActive(list, "2");
var targetList = [
{ id: "1", active: false },
{ id: "2", active: true },
{ id: "3", active: false },
{ id: "4", active: false }
];
assert.deepEqual(newList, targetList);
});
});
Your function isn't returning anything, so any variables you try to set to the result will be set as undefined.
To fix this, simply add a return statement to the end of your function.
function markActive(list, value) {
list.forEach((id) => {
if (id.active = (id.id === value)) {
return true;
} else {
return false;
}
});
return list; // return the updated list
}
NOTE: It's worth mentioning that because the array is referenced, you're modifying the values in-place. This is why the array you defined outside the function still had updated results even though you weren't logging the returned value. This can have unintended side effects if you were to run the markActive() function several times on the same list. If you want a new list to be returned, look into ways of copying and deep copying arrays in Javascript.

Categories

Resources