Why is setState not working in React functional component [duplicate] - javascript

This question already has an answer here:
How does React useState hook work with mutable objects
(1 answer)
Closed 2 years ago.
const [bullyTypes, setBullyTypes] = React.useState([
{ value: "Exotic", isChecked: false },
{ value: "Pocket", isChecked: false },
{ value: "Classic", isChecked: false },
{ value: "Standard", isChecked: false },
{ value: "Extreme", isChecked: false },
{ value: "XL", isChecked: false },
]);
const handleBullyTypeChange = (event) => {
let bullyTypesCopy = bullyTypes;
bullyTypesCopy.forEach((bullyTypeCopy) => {
if (bullyTypeCopy.value === event.target.value) {
bullyTypeCopy.isChecked = event.target.checked;
}
});
setBullyTypes(bullyTypesCopy); // not working
setBullyTypes([
{ value: "Exotic", isChecked: true },
{ value: "Pocket", isChecked: false },
{ value: "Classic", isChecked: false },
{ value: "Standard", isChecked: false },
{ value: "Extreme", isChecked: false },
{ value: "XL", isChecked: false },
]); // this is working even though bullyTypesCopy variable has the same value with this array of objects.
};
When I pass the exact array as argument to setBullyTypes it works
but when i pass the variable containing the array it wont work even though they have the same value
Please help me. Thanks

In the event handler bullyTypesCopy is copied by reference, and forEach is not doing what you expect, it just iterate over the array entries. I think what you need to do is use map in order to actually get a new content based on your condition. In this way setBullyTypes should work.
Please try the following example
const [bullyTypes, setBullyTypes] = React.useState([
{ value: "Exotic", isChecked: false },
{ value: "Pocket", isChecked: false },
{ value: "Classic", isChecked: false },
{ value: "Standard", isChecked: false },
{ value: "Extreme", isChecked: false },
{ value: "XL", isChecked: false },
]);
const handleBullyTypeChange = (event) => {
let bullyTypesCopy = bullyTypes.map((bullyTypeCopy) => {
if (bullyTypeCopy.value === event.target.value) {
return { ...bullyTypeCopy, isChecked: !event.target.checked };
}
return { ...bullyTypeCopy };
});
setBullyTypes(bullyTypesCopy); // not working // this should work now
setBullyTypes([
{ value: "Exotic", isChecked: true },
{ value: "Pocket", isChecked: false },
{ value: "Classic", isChecked: false },
{ value: "Standard", isChecked: false },
{ value: "Extreme", isChecked: false },
{ value: "XL", isChecked: false },
]); // this is working even though bullyTypesCopy variable has the same value with this array of objects.
};

Related

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)

Updating complicated Object with useState

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]);

Push objects to new array with checked: true, if in mapped array

Can I please get some help with this scenario?
An array of strings
A function that maps the array of strings and applies to each one of them a name, key and adds one more object "checked: false".
A function that takes the mapped array and transforms it according to the argument passed, storing the value in to another array and changing the "checked" to value "true"
Ex:
const defaultProducts = [
"Laptop",
"Tablet",
"Phone",
"Ram",
"SSD",
"RasberyPi",
"Desktop",
"TV",
"Monitor"
];
const getDefaultProducts = () => {
return defaultProducts.map(products => {
return {
name: products,
checked: false
};
});
};
console.log(getDefaultProducts())
let forSale = []
function useProduct(product){
if(product in getDefaultProducts()) {
return{
product: forSale.push(product),
checked: true
};
};
return {product};
}
console.log(useProduct("Laptop"))
console.log(forSale)
returns
[ { name: 'Laptop', checked: false },
{ name: 'Tablet', checked: false },
{ name: 'Phone', checked: false },
{ name: 'Ram', checked: false },
{ name: 'SSD', checked: false },
{ name: 'RasberyPi', checked: false },
{ name: 'Desktop', checked: false },
{ name: 'TV', checked: false },
{ name: 'Monitor', checked: false } ]
{ product: 'Laptop' }
[]
Should return:
[ { name: 'Laptop', checked: false },
{ name: 'Tablet', checked: false },
{ name: 'Phone', checked: false },
{ name: 'Ram', checked: false },
{ name: 'SSD', checked: false },
{ name: 'RasberyPi', checked: false },
{ name: 'Desktop', checked: false },
{ name: 'TV', checked: false },
{ name: 'Monitor', checked: false } ]
{ product: 'Laptop' }
[{name:"Laptop", checked: true}]
In the part where you checked the condition as if product in getDefaultProducts () will not work since getDefaultProducts is an array of objects. You are comparing strings with each object such as:
"Laptop" === { name: "Laptop", checked: false }
which will return false always. Instead you can use find function:
function useProduct(product){
getDefaultProducts().find(el => {
if (el.name === product) {
el.checked = true
forSale.push(el)
}
});
return product;
}
Try with this:
function useProduct(product){
const found = getDefaultProducts().find(p => p.name === product)
if (found) {
found.checked = true
forSale.push(found)
}
return {product}
}

Best Approach to filter out data from array

I want to filter out data from an array based on certain condition.
Check if both 'isChecked' & 'isPrimaryKey' is true and if 'rename' is not empty then in the output array need to show rename otherwise show name.
The below code working perfectly for me. I want to know is there any better approach.
Also want to know is there any fault in this approach (using map for iterating over limited number of object)
let columnList = [
{ id:1, name: "Id", rename: "", isChecked: true, isPrimaryKey: true },
{ id:2, name: "Name", isChecked: false, isPrimaryKey: true },
{ id:3, name: "age", rename: "Age", isChecked: true, isPrimaryKey: true },
{ id:4, name: "Designation", isChecked: true, isPrimaryKey: false },
{ id:5, name: "Salary", isChecked: false, isPrimaryKey: true },
{ id:6, name: "Department", isChecked: true, isPrimaryKey: false },
{ id:7, name: "Project", isChecked: false, isPrimaryKey: false }
];
let data = columnList.map(d => {
if(d.isChecked && d.isPrimaryKey) {
return (d.rename && d.rename !== '') ? d.rename : d.name;
}
});
data = data.filter(item => item);
console.log(data);
Output: ["Id", "Age"]
You should use array.reduce instead of array.map + array.filter.
Array.map - This function will return an array of n element with returned value. It can return partial/processed value.
Array.filter - This function is used to filter items in array. This will return n elements that match the case but will not change/process them.
Array.reduce - This function is meant to reduce the number of items in array. In doing this, you can manipulate items and return a custom value.
Also for the case where you need to check if rename is available then return it else return name, you can achieve it using .push(d.rename || d.name)
let columnList = [
{ id:1, name: "Id", rename: "", isChecked: true, isPrimaryKey: true },
{ id:2, name: "Name", isChecked: false, isPrimaryKey: true },
{ id:3, name: "age", rename: "Age", isChecked: true, isPrimaryKey: true },
{ id:4, name: "Designation", isChecked: true, isPrimaryKey: false },
{ id:5, name: "Salary", isChecked: false, isPrimaryKey: true },
{ id:6, name: "Department", isChecked: true, isPrimaryKey: false },
{ id:7, name: "Project", isChecked: false, isPrimaryKey: false }
];
let data = columnList.reduce((p, d) => {
if(d.isChecked && d.isPrimaryKey) {
p.push(d.rename || d.name);
}
return p;
}, []);
console.log(data)
But if you still wish to go for Array.map + array.filter, you can try something like this:
let columnList = [
{ id:1, name: "Id", rename: "", isChecked: true, isPrimaryKey: true },
{ id:2, name: "Name", isChecked: false, isPrimaryKey: true },
{ id:3, name: "age", rename: "Age", isChecked: true, isPrimaryKey: true },
{ id:4, name: "Designation", isChecked: true, isPrimaryKey: false },
{ id:5, name: "Salary", isChecked: false, isPrimaryKey: true },
{ id:6, name: "Department", isChecked: true, isPrimaryKey: false },
{ id:7, name: "Project", isChecked: false, isPrimaryKey: false }
];
let data = columnList
.filter(d => d.isChecked && d.isPrimaryKey)
.map(d => d.rename || d.name);
console.log(data)
The difference is in reading. When you read it, you read it as,
You are filtering list by isChecked and isPrimaryKey and the from the filtered, you are returning either rename or name
These functions are part of functional programming and their beauty comes when they improve readability of code. In your code, reader will have to read the logic to understand what you are trying to achieve, which beats the purpose.
A follow-up to Rajesh's answer: if you want the clean code of filter-then-map, but don't want to add extra iterations, Ramda offers support for transducers in many of its list-handling functions, including map and filter. The following version will iterate the list once, checking on each step if the item matches the filter, and if it does choosing the correct property via the function passed to map.
But the code is still nicely factored into the filtering step and then the mapping step.
let columnList = [
{ id:1, name: "Id", rename: "", isChecked: true, isPrimaryKey: true },
{ id:2, name: "Name", isChecked: false, isPrimaryKey: true },
{ id:3, name: "age", rename: "Age", isChecked: true, isPrimaryKey: true },
{ id:4, name: "Designation", isChecked: true, isPrimaryKey: false },
{ id:5, name: "Salary", isChecked: false, isPrimaryKey: true },
{ id:6, name: "Department", isChecked: true, isPrimaryKey: false },
{ id:7, name: "Project", isChecked: false, isPrimaryKey: false }
];
const getCols = R.into([], R.compose(
R.filter(d => d.isChecked && d.isPrimaryKey),
R.map(d => d.rename || d.name)
))
const cols = getCols(columnList)
console.log(cols)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
You also asked if there was an issue with your approach. There might be. As well as the extra iterations already discussed, there are two more concerns: First, using filter with item => item will disallow not just the null/undefined values but also any false-y value, most notably 0. So if you were trying to extract a list in which 0 might be a reasonable value, this will exclude it. Second, in functional programming, it's generally considered a bad habit to reassign variables. So data = data.filter(...) is much frowned-upon.

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