Looping through an array to get the data for specific - javascript

I want to loop through an array of objects to get a specific id and then render its data in a component in react, why it cannot be mapped, what is wrong here?
const projectData = projects.find(element => {
return element.id === projectId;
});
return (
{projectData.map(project => {
return <ProjectData key={project.id} {...project}></ProjectData>;
})}
)

find is returning either null or an object. you need an array to loop over the map. use the filter operator instead
const projectData = projects.filter(element => {
return element.id === projected;
});

The find() method returns the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
So you don't need to use map for rendering.
const projectData = projects.find(element => {
return element.id === projectId;
});
return (
{projectData && <ProjectData key={projectData.id} {...projectData}></ProjectData>}
)
If there are many elements that satisfies the testing function please use the filter method, not find method.
In this case, the return value is the array, so you should use map for rendering.
const projectData = projects.filter(element => {
return element.id === projectId;
});
return (
{projectData.map(project => {
return <ProjectData key={project.id} {...project}></ProjectData>;
})}
)

Related

React "magically" updates two states instead of one

I have two states defined like so:
const [productProperties, setProductProperties] = useState<
PropertyGroup[] | null
>(null);
const [originalProductProperties, setOriginalProductProperties] = useState<
PropertyGroup[] | null
>(null);
The first one is supposed to be updated through user input and the second one is used later for a comparison so that only the PropertyGroup's that have changed values will be submitted via API to be updated.
I have done this a thousand times before, but for some reason when I change the name value for a PropertyGroup and update the state for 'productProperties' like so:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i].name = {
...update[i].name,
[selectedLocale]: e.currentTarget.value,
};
setProductProperties([...update]);
}
}
The state of originalProductProperties also updates. Why? setOriginalProductProperties is never called here, I am also not mutating any state directly and I use the spread operator to be sure to create new references. I am lost.
Preface: It sounds like the two arrays are sharing the same objects. That's fine provided you handle updates correctly.
Although you're copying the array, you're modifying the object in the array directly. That's breaking the main rule of state: Do Not Modify State Directly
Instead, make a copy of the object as well:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update); // (No need to *re*copy the array here, you've already done it at the top of the function)
}
}
Or, since you have that i !== -1 check there, we could copy the array later so we don't copy it if we don't find the group matching itemId:
(e, itemId) => {
const i = productProperties.findIndex((group) => group.id === itemId);
if (i !== -1) {
const update = [...productProperties];
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update);
}
}
FWIW, in cases where you know there will be a match, map is good for this (but probably not in this case, since you seem to indicate the group may not be there):
(e, itemId) => {
const update = productProperties.map((group) => {
if (group.id === itemId) {
// It's the one we want, create the replacement
group = {
...group,
[selectedLocale]: e.currentTarget.value,
};
}
return group;
});
setProductProperties(update);
}
Or sometimes you see it written with a conditional operator:
(e, itemId) => {
const update = productProperties.map((group) =>
group.id === itemId
? { // It's the one we want, create a replacement
...group,
[selectedLocale]: e.currentTarget.value,
}
: group
);
setProductProperties(update);
}

Only push the object which is not in the array yet

This is my function:
const multiSelect = value => {
let tmpArr = [...selectedPeople];
if (tmpArr.length === 0) {
tmpArr.push(value);
} else {
tmpArr.map(item => {
if (item.id !== value.id) {
tmpArr.push(value);
} else {
return;
}
});
}
setSelectedPeople(tmpArr);
};
I want to check the array for the new value by comparing it with all items. If value === item item the loop function should return, but if the value is not in the array yet, it should push it.
This is a big problem for me but I assume it is a small problem for you guys.
Use Array.every() to check if the array doesn't contain an item with the same id:
const multiSelect = value => {
const tmpArr = [...selectedPeople];
if(tmpArr.every(item => item.id !== value.id)) {
tmpArr.push(value);
}
setSelectedPeople(tmpArr);
};
However, this means that you're duplicating the array needlessly, while causing a re-render, that won't do a thing. So check if the item is already a part of selectedPeople by using Array.some(), and if it does use return to exit the function early. If it's not continue with cloning, and updating the state:
const multiSelect = value => {
if(tmpArr.some(item => item.id === value.id)) {
return;
}
const tmpArr = [...selectedPeople];
tmpArr.push(value);
setSelectedPeople(tmpArr);
};
Use find to check if the item is already in the array. Also, there's no need to make a copy of the source array:
const multiSelect = value => {
if (!selectedPeople.find(item => item.id === value.id))
setSelectedPeople(selectedPeople.concat(value))
}
Another approach.
const
multiSelect = value => setSelectedPeople([
...selectedPeople,
...selectedPeople.some(({ id }) => id === value.id)
? []
: [value]
]);

destructuring props in component getting different result

New to react world, trying to learn destructuring, have been reading about it but stuck here,
if i do it like this function MList({action}) { // const data = [action];} i am just getting 'cameras'. So how to destructure and get same result as with props below
this is Mcard.js:
<Box pt={1}>
<MList
action="cameras"
/>
</Box>
This is inside MList komponent:
i want to destructure this code ( works gives 'name' and 'ident'):
function MList(props) {
const initialize = () => {
const data = props[props.action];
if (!data || data.length < 1) {
return;
}
data.map((e) => {
collapseStates["" + e.name + e.ident] = false;
return;
});
setCollapseS(collapseS);
};
}
I don't know React but destructuring the arguments should be something like the following
function MList({action, ...tail}) {
const initialize = () => {
const data = tail[action];
if (!data || data.length < 1) {
return;
}
data.map(({name, ident}) => {
collapseStates["" + name + ident] = false;
return;
});
setCollapseS(collapseS);
};
}
Also I would suggest using data.forEach instead of data.map if you don't need to save the result in another array
Nikita is correct about using props["action"] or props.action to grab the values. But you can actually destructure the props right inside of the function declaration like so:
function MList({ action, ...other props }) {
// Can now use action directly instead of props.action
}
It is also worth noting that array and object destructuring is not react specific, this is just how javascript works.
Edit: Accessing the action variable from here will give you "cameras" as a result because that is what you passed into the props

Using Array.find() to try and dynamically grab a specific object value and return it based on Object name, nothing returning from function

I have an array of object values each with it's own sub array of objects.
I'm attempting to use the Array.find() method to target each array node specifically and return a value from it's subarray based on name.
I have my function all written out but nothing is being returned and I keep getting a warning stating:
Expected to return a value at the end of the function(array-callback-return)
And I'm not entirely sure what I'm missing.
The function and data structure for reference as well as a CodeSandbox:
const subjectTypes = [
{
name: "pokemon",
details: [
{
type: "subtitle",
value: "This is the subtitle for pokemon"
}
]
},
{
name: "marvel",
details: [
{
type: "subtitle",
value: "This is the subtitle for marvel"
}
]
}
];
const setCustomValue = (location) => {
let value;
switch (location) {
case "/pokemon":
return (value = subjectTypes.find(function (subject, idx) {
if (subject.name === "pokemon") return subject.details.value;
}));
case "/marvel":
return (value = subjectTypes.find(function (subject, idx) {
if (subject.name === "marvel") return subject.details.value;
}));
default:
return null;
}
};
const customValue = setCustomValue("/pokemon");
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Dynamically Populated Content Below:</h2>
<p>{customValue}</p>
</div>
);
}
Also I'm aware that this may appear like over engineering and that I could easily just use a .map() or similar to print out values, but I'm using this as a simplified version of a larger problem I'm trying to solve that requires something like the .find() method to return a specific result.
Array#find() expects a true or false from the callback. Or at the very least a truthy or falsy value.
The ESLint rule array-callback-return reports a related problem. The callback only returns something from the if, not when the condition is not met. This is normally valid - no return makes the function implicitly return undefined. Since this value is falsy, find will ignore this item and continue with the next one. However, the ESLint rule is there to make these returns explicit and prevent mistakes, in case it is actually a bug that a branch of code does not return anything.
In the callback the return value is subject.details.value but the actual path is subject.details[0].value because subject.detailsis an array. Therefore the only return you have, produces undefined.
It's not possible to both locate a value with .find() and modify what is returned at the same time. You need to return the value first and then get a sub-section of it.
No need to declare and assign value when it is never used.
To address these, here is the code a bit cleaned up:
const setCustomValue = (location) => {
let value;
switch (location) {
case "/pokemon":
value = subjectTypes.find(function (subject, idx) {
return subject.name === "pokemon";
});
return value.details[0].value;
case "/marvel":
value = subjectTypes.find(function (subject, idx) {
return subject.name === "pokemon";
});
return value.details[0].value;
default:
return null;
}
};
To avoid some of the repetition, you can extract the .find() and return logic outside the switch:
const setCustomValue = (location) => {
let name;
switch (location) {
case "/pokemon":
name = "pokemon";
break;
case "/marvel":
name = "marvel";
break;
default:
return null;
}
const value = subjectTypes.find((subject) => subject.name === name);
return value.details[0].value;
};
The whole switch can be eliminated by using a simple lookup instead:
const paths = {
"/pokemon": "pokemon",
"/marvel": "marvel",
}
const setCustomValue = (location) => {
let name = paths[location];
if (!name)
return null;
const value = subjectTypes.find((subject) => subject.name === name);
return value.details[0].value;
};
And even more minimal using optional chaining and the nullish coalescing operator
const paths = {
"/pokemon": "pokemon",
"/marvel": "marvel",
}
const setCustomValue = (location) =>
subjectTypes.find((subject) => subject.name === paths[location])?.details[0].value ?? null;
Find returns you the element, based on a boolean query. So you have to find through name, and then get that element's value
Try:
return (value = subjectTypes.find( function (subject, idx) {
return subject.name === "pokemon";
}).details.value);
A cleaner way of writing this, in my totally personal opinion:
value = subjectTypes.find(
subject => subject.name === "pokemon"
).details.value
The find() method returns the value of the first element in the
provided array that satisfies the provided testing function. If no
values satisfy the testing function, undefined is returned.
it will return all the element that satisfies the provided testing function
try like this :
return subjectTypes.find(function (subject,idx) {
return subject.name === "pokemon"
}).details[0].value
details it is not an object it is an array so you have to select the oject inside the array then get the value of it
or you can simply use arrow function like this
reuturn subjectTypes.find( (subject,idx)=>
subject.name === "pokemon"
).details[0].value

how to get rid of an array inside an array in javascript

const addressZip = person.get('addresses').filter((address) => address.get('legacy_id')).filter((newAddress) => (
getZIPErrors(newAddress.get('zip'))
))
when this function is executed it returns me as an
[array(0)] if it has no error
when it has an error it returns me as an [array(1)].
Instead of returning an array inside an array I just want to return a single array in this way if it has no error [] and if it has an error it should be like ['invalid']
You can implement a concatAll method within Array constructor, then use it to flatten your result:
Array.prototype.concatAll = function() {
return this.reduce((acc, curr) => acc = acc.concat(curr))
}
const addressZip = person
.get('addresses')
.filter(address => address.get('legacy_id'))
.filter(newAddress => getZIPErrors(newAddress.get('zip')))
.concatAll()

Categories