Rerendering mapped array based on order - javascript

So I have an array of objects that I need to display in a specific order and be able to organize. The functionality is there and it works, but I can't get it to render appropriately.
Basically I have:
const [objects, setObjects] = useState([]);
return (
<>
{objects.map(object =>
<Component
object={object}
/>
)}
</>
);
Now I obviously have this array filled with data and it renders fine, but when I try to reorganize the array, nothing seems to change on the UI.
I'm reorganizing by way of splice. I identify the object that I'm moving, splicing it out and splicing it back in at a different index (not actually sure if I need this temporary variable):
function reorganize(sourceIndex, targetIndex){
const object = objects[sourceIndex];
const temp = objects;
temp.splice(sourceIndex, 1);
temp.splice(targetIndex, 0, object);
setObjects(temp);
}
When I use console.log() on objects, I can see that the order has changed. But as I said, nothing changes on the UI. Any thoughts on how to make this work?

in the reorganize function change this
const temp = [...objects];
Don't forget that it is an array.

You are mutating the state - temp is not a copy of objects, but is still directly referencing it. In React you should not directly mutate the state - correct code would be temp = [...objects]. Also be aware that this will only create a shallow copy - if you are handling deeply nested arrays / objects, use an external library like Lodash's cloneDeep.

Related

change array value in specific index reactjs

I have a state like this
const [tmp,setTmp] = useState(
{status:false},
{status:false},
{status:false},
{status:false}
)
how can I change status in tmp[2] ?
You can provide a completely new list of values with the help of the spread syntax to set the new value with a different reference.
// Simply shallow copies the existing list. This is simply to make a new reference.
const newTmp = [...tmp]
// The object references inside aren't changed. But React will rerender children components anyway when the bit that uses `tmp` is rerendered.
newTmp[2].status = true
// You can also do this to get a new reference to the object
newTmp[2] = {status: true}
setTmp(newTmp)
The reason you want to provide a new value with a different reference is per React's requirement: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
If you simply do the following, it is not adequate since the reference to the array remains the same and React does not do deep equality check to find out if the new value is different.
tmp[2].status = true
setTmp(tmp) // Does not adhere to React docs and will probably fail at rerendering
P.S. Among the JS data types, Array, Function, and Object are references. Do read up on checking equality with references if this isn't familiar to you.
As long as the array is a different reference to the existing, React will detect this as a change. So you can clone the array, and then update the item at the index like shown by Daniel.
Or you can even use the normal JS array method slice to build a new array.
eg.
setTmp([...tmp.slice(0, 2), {status: true}, ...tmp.slice(3)]);
If you do this a lot, you could convert the above into a simple helper function.
function changeArrayItem(arr, ix, value) {
return [...arr.slice(0, ix), value, ...arr.slice(ix +1)];
}
//use
setTemp(changeArrayItem(tmp, 2, {status:true});

How to render multiple HTML parts with a plain Javascript function

This is a static website with hundreds of pages. I need to render elements like a topnav or a newsletter or a strap of content and changing those contents periodically, from JS.
This is what I tried:
const components = {
compartirEnFlex: `<h4>Newsletter</h4>`,
newsletterEs: `<h4>Compartir</h4>`,
}
const ids = ['newsletterEs', 'compartirEnFlex', 'infoArticulo', 'infoDeLaWebEnFlexIzq']
function renderComponents(objWithComp, idsArr){
return idsArr.map(function(id){
for(let component in objWithComp){
let arrOfIds = Object.keys(objWithComp);
arrOfIds.map(key => key)
if(id === key){
document.getElementById(id).insertAdjacentHTML('afterbegin', objWithComp[id])
}
}
})
}
renderComponents(components, ids);
Each id has its counterpart in the HTML structure. When I do this individually it works. However, I have to handle this in an elegant way (and there is no possibility for a JS framework like React in this project).
Thanks for the help!
When you run your code, you'll see the error Uncaught ReferenceError: key is not defined in the console.
That's because key in if(id === key) is not defined. The line arrOfIds.map(key => key) returns the same exact array as arrOfIds because Array.prototype.map "returns a new array populated with the results of calling a provided function on every element in the calling array."
Here, you don't assign that new array to a variable, so nothing happens. Even if it was, that new array would be a copy of arrOfIds because your mapping function (key) => key returns key for every key -- meaning that the output is the same as the input.
However, that's not an issue here. If I understand your question correctly, then this demo should show an example of what you're trying to accomplish. If that's what you want to achieve, then here's a solution:
First, you don't need to iterate for component in objWithComponent inside idArr -- you're already doing that in the idArr. You don't need the ids array either, because you can get the keys of the components from the components object using Object.keys().
Let's say your HTML looks something like this:
<div>
<div id="newsletterEs"></div>
<div id="compartirEnFlex"></div>
<div id="infoArticulo"></div>
<div id="infoDeLaWebEnFlexIzq"></div>
</div>
Then, using Object.keys(components) to get an array of the ids of the components that you have, you can map those to HTML tags. In fact, map is not necessary here because map returns a new array, and unless you need that array later, there's no reason to use map. Instead, you can use Object.prototype.forEach.
Here's what that would look like:
const components = {
compartirEnFlex: `<h4>Newsletter</h4>`,
newsletterEs: `<h4>Compartir</h4>`,
}
function renderComponents(objWithComp) {
Object
.keys(components)
.forEach((id) => {
const element = document.getElementById(id)
const component = objWithComp[id]
if (component && element) {
element.insertAdjacentHTML('afterbegin', component)
}
})
}
renderComponents(components)
Then, when you call renderComponents, you can pass just the components argument, and only render the components for which divs with corresponding ids exist with an if statement.

Passing associative array as props not working

I have a React application which handles rooms and their statistics.
Previously, I had the code set up to pass as props to the next component:
the raw statistics (not a concern for the question)
an array of all the rooms set up as follows
I figured it would be simpler for me, though, to have the list of all rooms as an associative array where the keys of each element is the same as the ID it contains. To do that, I utilized a code similar to this in a for loop:
roomsList[rooms[v].ID] = rooms[v];
So that the result would be:
[a001: {...}, a002: {...}, ...]
I then proceeded to pass this style of array, and not the standard one with a numeric index, as a prop to the next component as such:
<StatsBreakdown stats={computedStats.current} roomsList={roomsList} />
BUT
Now, the next component sees that prop as an empty array.
Even more weirdly, if I initialize that roomsList array with a random value [0] and then do the same process, I end up with:
I cannot cycle through the array with .map, and, according to JS, the length is actually 0, it's not only Google Chrome.
Is there something I'm missing about the way JSX, JS or React work?
Your original roomsList was an array of objects, whose indices were 0,1,2 etc. roomsList[rooms[v].ID] = rooms[v]; implies you are inserting elements not using a number but an alphanumeric string. Hence your resulting array is no longer an array but an object.
So we can cycle over the object using Object.keys().
const renderRoomDets = Object.keys(roomsList).map(room => {
roomOwner = roomsList[room].owner_id;
return (
<div>
<p>{`Room Owner ${roomOwner}`}</p>
</div>
);
});
But I believe your original form is ideal, because you are reaping no special benefits from this notation.
A better alternative maybe using .find() or .findIndex() if you want iterate over an array based on a specific property.
const matchedRoom = roomsList.find(room => room.ID === 'Srf4323')
Iterate the new array using its keys not indexes.
Or even better store your data in an actual object instead of an array since you're using strings for ids.
First define your object like so:
let data = {};
Then start adding records to it. I'd suggest deleting the ID attribute of the object since you're storing it in the key of your record, it's redundant, and it won't go anywhere unless u delete the entry.
data[ID] = row;
To delete the ID attribute (optional):
row.ID = null;
delete row.ID;
Then iterate through it using
for(let key in data){}

React: Given an array, render the elements in reverse order efficiently

I currently render a list in the typical React-style. The list is passed as an array prop, and I map over it like so:
{this.props.myList.map(createListItem, this)}
So when a new element is added, it appears like the latest item was added to the end of the list.
I would like it so the latest item appears at the top. i.e. everything appears in reverse-chronological order.
The two options I've come up with so far are:
1) Reverse the list, creating a new array each time something is added, and pass this reversed list as the prop.
2) Use shift.
But they're both unappealing because of performance.
I'm not aware of Javascript supporting mapping in reverse order. I've been trying a for-loop but I haven't been able to get it to work.
What is the idiomatic way to render an array in reverse order in React?
If you choose to reverse the list using reverse(), shift() or splice(), you should make a shallow copy of the array first, and then use that function on the copy. Props in React should not be mutated.
For example:
[...this.props.myList].reverse().map(createListItem, this)
or
this.props.myList.slice(0).map(createListItem, this)
(this should really be a comment, but I don't have the points to do that yet :))
If you need to display a list in the UI in reverse order you can also use
flex-direction: row-reverse;
or
flex-direction: column-reverse;
As others have pointed out the humble reverse method does the job for most part. I currently ran into the same issue and I must say that using array.reverse() atleast in Chrome, the performance hit wasnt seen as such. In my opinion its better than using a loop to sort a list in reverse order.
array.reverse()
When using mobx as a store one can create a computed property for reversed array that will be reevaluated and memoized each time original observable array changes.
Store
import { observable, computed } from 'mobx';
class MyStore {
#observable items = [1, 2, 3];
#computed get itemsReversed() {
return this.items.slice().reverse();
}
}
export default MyStore;
Rendering
import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
#inject('myStore') #observer
class List extends Component {
render() {
const { myStore } = this.props;
const { itemsReversed } = myStore;
return (
<div className="list">
{itemsReversed.map(item => (
<div className="list-item">{item}</div>
))}
</div>
);
}
}
export default List;
According to the official documentation this is a preferred way to reverse an array:
Unlike the built-in implementation of the functions sort and reverse, observableArray.sort and reverse will not change the array in-place, but only will return a sorted / reversed copy. From MobX 5 and higher this will show a warning. It is recommended to use array.slice().sort() instead.
Some how while using array.reverse() the order was changing whenever something in state changed.I went with flexDirection:'column-reverse' which worked fine and you dont need to mess with the array data aswell.
Add the new elements at the beginning of the array:
array.splice(0,0,'value to add at beginning');
Or call a for loop with an immediately invoked function:
{(() => {
for(...) {
return (<i>{whatever}</i>)
}
})()}
Keep pushing at the array, and when rendering, you can simply use the
Array.reverse()
here the documentation
Remind that it will mutate the original one
Simply first create copy of array using slice and apply reverse function when using map.
For example:
var myArr = [1,2,3,4,5]
myArr.slice(0).reverse().map((element, index) => {
console.log(element);
});

Cloning and manipulating nested object using forEach, map, or filter without modifying original object

I'm trying to get a handle on using .map, .filter to clone and modify a big nested JSON object based on a deeply nested property. With the below code, the original data and the filtered data both end up getting modified, but I'm trying to leave the original data intact. What I'm hoping to do is have the deeply nested concerns array emptied in the final filtered object for a given id, leaving the original data as the original complete data set.
var data {...};
var dataFilter = function dataBuild(data) {
var newData = data;
newData.service_requests = newData.service_requests.map((request) => {
request.concerns = request.concerns.filter((concern) => {
return concern.building_id == 2
});
return request;
});
return newData;
};
var filtered = dataFilter(data);
Here's a fiddle with what I'm trying to do with the full object in there.
http://jsbin.com/doyoqinoxo/edit?js,console
When you do:
var newData = data;
you are simply making a second reference to the same object, so:
newData.service_requests = ...
overwrites the value in data.service_requests.
It seems you want newData to be a copy of data, not a reference to the same object. There are plenty of posts here on how to copy a nested object (a so–called deep copy), e.g. What is the most efficient way to clone an object?, but please ignore the accepted answer unless you are using jQuery. Use one of the other answers, like this one.
JSIterator .map() creates the new array with the same number of elements or does not change the original array. There might be the problem with referencing if there is object inside the array as it copies the same reference, so, when you are making any changes on the property of the object it will change the original value of the element which holds the same reference.
The solution would be to copy the object, well, array.Splice() and [...array](spread Operator) would not help in this case, you can use JavaScript Utility library like Loadash or just use below mention code:
const newList = JSON.parse(JSON.stringify(orinalArr))

Categories

Resources