Iterating numerical value and send as props - javascript

I have a array with values that I use a forEach to pass values as props to a component. I also want to send a value as props, and increase it by 1 for every iteration. Currently I have tried:
this.state = {
index: 0
}
let news:any[] = []; //container for other items for the loop
let indx = this.state.index;
this.state.listItems.forEach((myArrWItems) => {
indx = +1;
news.push(<MyComponent
index = {indx}
/>);
});
However, the value sent in as props is always 1. Any help?

Your index state variable is not necessary there. Since you want to create an array from an existing one, you should be using the map function, as shown in the official documentation.
This function will give you the index of your element right into its callback, as a the second argument.
Your coded can be reduced to a single line :
const news = this.state.listItems.map((item, index) => <MyComponent key={index} index={index + 1}/>)
Do not forget to set the key prop of your component when making an array.
The <MyComponent index/> syntax is the short version of <MyComponent index={index}/>

You never update your index variable, you're just assigning +1 to the indx variable which is never mutated. What you need is to update your state and increment your indx variable that way and push your value into the array on setState callback, but I recommend you to use the current index array element within your forEach loop as it is the best way to do what you want to :
this.state.listItems.forEach((myListItem, index) => {
news.push(<MyComponent
key={index}
index={index+1}
/>);
});

Related

Spreading array to set a new state

I am working in React.JS and trying to set a new state by spreading array of objects and changing a value of one of them. I have an array of objects with key and another object as a value. Like this:
[
{"first": {
"backlog":[...]
}
},
{"second": {
"backlog":[...]
}
},
{"third": {
"backlog":[...]
}
}
]
I also have variable selected with one of object's key as a value. So I want to spread my entire array as a new state, but with a slight change to a backlog of exact object. The key of it's object is saved in selected variable. The problem is, I can't reach it the object I need. I tried to do it like this, but I understand where I was wrong:
setProjects((prevProjects) =>
([...prevProjects, [selected]: {...prevProjects[selected], backlog: [...prevProjects[selected].backlog, NewObj ]}])
)
Is it even possible to achieve? If yes, how?
You will have to find the index of the object with the key in the outer array, and then update that index while mapping over the state.
const index = projects.findIndex(obj => obj[selected]);
setProjects(
projects.map((project, i) => (
i === index
? { backlog: [...project.backlog, NewObj] }
: project
))
);
But consider if you could use an easier data structure to make manipulations of it easier.
const [projects, setProjects] = useState({
first: [...], // first backlog
second: [...], // second backlog
// etc
});
Then you can update with
setProjects({
...projects,
[selected]: [...projects[selected], NewObj]
});
You don't need to use the callback form (setProjects((prevProjects) =>) unless you've previously updated the projects state synchronously and the component hasn't re-rendered yet. Sometimes it's necessary, but usually it isn't.

Array.push is pushing only last object in the array

First i set ids as an object
attribute.map((item, index) => {
setIds({ ...ids, [index]: item.id })
})
then i try to push it in an array, but it is only pushing last object
let idsArr = []
Object.keys(ids).map(key => {
idsArr.push(ids[key])
})
one proposal can be to push the map result in array
idsArr.push(...Object.keys(ids).map(key => ids[key]));
Moreover when you set the id if you don't return nothing the map method should not be used.
A loop like foreach is a better choice
map method is used to get a "transformed" array
attribute.forEach((item, index) => {
setIds({ ...ids, [index]: item.id })
})
let idsArr = [];
let ids = {
'test': 1,
'test2': 2
};
idsArr.push(...Object.keys(ids).map(key => ids[key]));
console.log(idsArr);
Issue
You are enqueueing a bunch of React state updates inside a loop, using a normal update, and as each update is processed by React it uses the same value of ids from the render cycle they were all enqueued in. In other words, each update stomps the previous one and the last state update wins. This is the state value you see on the subsequent render cycle.
Solutions
Use functional state updates to enqueue each update and have each update from the previous state instead of the state from the callback closure.
Example:
attribute.forEach((item, index) => {
setIds(ids => ({
...ids,
[index]: item.id,
}))
});
With the correctly updated ids array state, you should be able to iterate and push values into the other array:
const idsArr = [];
Object.keys(ids).map(key => {
idsArr.push(ids[key]);
});
Or just get the array of values directly:
const idsArr = Object.values(ids);

Remove item from an array (useState Hook)

Have a useState hook like so:
const [array, updateArray] = useState([])
I know you can add items to the array using a spread operator like so.. (where item is whats being added)
updateArray(array => [...array, item])
how would you remove something from this array? Assuming you have the name of that array item. (remove based off value not index)
Thank you!
If you have a reference to the exact value that was inserted previously (say it's stored in a variable called itemToRemove), you can .filter it out:
updateArray(array.filter(item => item !== itemToRemove));
This works for both primitives and objects, but it's extremely strange to have an exact reference to an object currently in state. For objects, usually what you'll do is you'll need to figure out a way to identify the index of the element in state, probably with findIndex on a unique property, after which you'd set the new state to the array without that index, something like the following:
// say that the array contains objects with a unique 'id' property
// and you need to remove the item with the id of idToRemove
const index = array.findIndex(({ id }) => id === idToRemove);
if (index !== -1) {
updateArray([
...array.slice(0, index),
...array.slice(index + 1)
]);
}

useState creating multiple arrays

I am creating a dropdown filter to update the search results of a page using react hooks. Basically, I am passing an array with the options that the user chose from the dropdown menu. I am successfully updating the global state with the new arrays BUT my issue is useState creates a NEW array instead of merging the results with the previous state.
Above you can see, I made two calls with different filter options and the global state now holds 2 arrays. My goal is to have both arrays merged into one.
This is the function where the global state is being updated.
const Results = () => {
const [filterList, setFilterList] = useState([])
const setGlobalFilter = (newFilter) => {
let indexFilter = filterList.indexOf(newFilter);
// console.log("Top level index", indexFilter)
indexFilter ?
setFilterList([...new Set([...filterList, newFilter])]) :
setFilterList(filterList => filterList.filter((filter, i) => i !== indexFilter))
}
// console.log("TopFilterSelection:", filterList)
return (
<div>
<Filter setFilter={(filterList) => setGlobalFilter(filterList)}/>
</div>
)
}
I've been checking on using prevState like this:
...
setFilterList(prevState => [...new Set([...prevState, newFilter])]) :
...
But I don't know what I am doing wrong.
Any feedback would be much appreciated!
This happens because newFilteris an array, not a word.
Should be
setFilterList(previous => [...new Set([...previous, ...newFilter])])
Also this
let indexFilter = filterList.indexOf(newFilter);
always returns -1 if newFilteris an array (since you a sending brand new array each time), it's not a falsy value, be careful
Use the .concat method.
setFilterList(filterList.concat(newFilter))
Read more about it here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat

React/react hooks: child component is not re-rendering after the state is changed?

I am writing a code in react/react hooks that attempt to do the following.
Get an array of objects from the parent component as a prop
Set it as a state using useState hook.
Sort the state according to the intended filters (Time and Rating), and re-render the child component.
What I see is that the below code updates the state after sorting, but even though the state is updated, the child component that is dependent on the state would not re-render. I thought the child component would automatically re-render whenever the state is changed?
import React, {useState} from 'react';
import ProfilePostspreview from './ProfilePostspreview';
function ProfileNavigation(props){
const [newarray, setnewarray]=useState(props.parray); //'parray' object passed as props and saved as 'newarray' state
const otc = () => { //Function to sort the state by time and set a new state
let k=newarray;
setnewarray(k.sort((a, b) => (a.time > b.time) ? -1 : 1 ));
}
const orc = () => { //Function to sort the state by rating and then time and set a new state
let k=newarray;
setnewarray(k.sort((a, b) => (a.rating > b.rating) ? -1 : (a.rating === b.rating) ? ((a.time > b.time) ? -1 : 1) : 1 ));
}
return (
<div>
<div className="sm_options"> //Component to trigger the otc and orc functions
<div className="sm_button" id="order_t" onClick={otc}>Sort by time</div>
<div className="sm_button" id="order_r" onClick={orc}>Sort by rating</div>
</div>
<div className="posts_preview_columns_p"> //This is dependent on the 'newarray' state but is not re-rendering even after the state is sorted and updated?
{newarray.map(na=>
<ProfilePostspreview
postThumbnail={na.photolink}
rating={na.rating}
time={na.time}
target={na.target}
/>
)}
</div>
</div>
);
}
export default ProfileNavigation;
What might be the reason for this? Is there something wrong with the code, or is sorting the state not considered powerful enough for React to re-render the child component? If latter is the case, what can be done to force re-render after sorting?
Any advice? Thanks!
array::sort
The sort() method sorts the elements of an array in place and
returns the sorted array. The default sort order is ascending, built
upon converting the elements into strings, then comparing their
sequences of UTF-16 code units values.
What this means for you is the order of elements stored in an array may change, but the array is sorted in place which means the same array reference is returned (unlike other array functions that return new arrays).
React reconciliation occurs by examining state and props and makes a holistic assumption that if the next state/prop references haven't changed then the values haven't changed and thus returns the last computed rendered DOM. This is the important detail of updating react state... each update needs to reference a new object.
In your case you are merely saving the reference to the current array in state, mutating it, and resaving it. Since the reference is stable and doesn't change, react doesn't re-render.
const otc = () => {
let k = newarray; // <-- saved array reference!!
setnewarray(k.sort((a, b) => (a.time > b.time) ? -1 : 1 ));
}
The correct react way is to copy the current array values into a new array so it will have a new object reference.
const otc = () => {
const newSortedArray = [...newArray].sort(
(a, b) => (a.time > b.time) ? -1 : 1
); // spread old array values into new array, then sort
setNewArray(newSortedArray);
}
React checks for changes in props and state by doing a shallow object equality check. If you set the state to the same object you received from state, React assumes you aborted your change and doesn't do anything, even if the properties on the object changed.
The key is that the sort() method sorts the array in place, and returns a reference to the same array. So React sees it as the same array object, even though the orser of its entries is different.
The solution is to create a new array:
let k = [...newarray];
When k is passed to setnewarray, React sees that it is an entirely different object, and triggers the rerender.
Sort() work in place, which means you won't get what you want via directly return.
By the way, you can write the sort() in a better format as below
const otc = () => {
const result = [...newarray];
result.sort((a, b) => b.time - a.time);
setNewarray(result);
};
const orc = () => {
const result = [...newarray];
result.sort((a, b) =>
a.rating !== b.rating ? b.rating - a.rating : b.time - a.time
);
setNewarray(result);
};
Try it online here:

Categories