Array order changed after multiple array.sort - javascript

I have a code to sort array
const timeSorted = wheater.list.sort((z,x)=>
{
return z.dt- x.dt
})
console.log(timeSorted)
Output that i get is sorted
but if i add another sort like this
const timeSorted = wheater.list.sort((z,x)=>
{
return z.dt- x.dt
})
const tempSorted = wheater.list.sort((a,b)=>
{
return a.main.temp - b.main.temp
})
console.log(timeSorted)
const timeSorted become tempSorted
How can i fix that?

The array is sorted in place.
In order to not have it changed, you need to create a copy of the previous array using oldArray.slice().
const tempSorted = wheater.list.slice().sort((a,b)=>
{
return a.main.temp - b.main.temp
})

Related

Add data into Array in local storage, React

I'm trying to store the page Id in an array stored in local storage every time a user load a page.
I have my array, it create one if needed but for some reasons it does not update the array in new page load and keeps the first page Id.
I want to add the page id in that array on every page load if the id is not already in that array.
I've tried a lot of things but it seems like I don't understand something, any help ? Thanks
Here is my code
const [isPostId, setItems] = useState([postId]);
useEffect(() => {
//const items = JSON.parse(localStorage.getItem('items'));
if (JSON.parse(localStorage.getItem('isPostId')) == null) {
localStorage.setItem('isPostId', JSON.stringify(isPostId));
}
if (!isPostId.includes(postId)) {
JSON.parse(localStorage.getItem('isPostId'))
localStorage.setItem('isPostId', JSON.stringify(isPostId));
} },[isPostId]);
EDIT: It works now, looks like I was confused about how localStorage works, now it's clear thanks for your help everyone
Both are working:
useEffect(() => {
const storageKey = "isPostId";
const json = localStorage.getItem("isPostId");
const previousPosts = json ? JSON.parse(json) : [];
const filtered = previousPosts.filter((it) => it !== postId);
const updatedPosts = [...filtered, postId];
const stringifyed = JSON.stringify(updatedPosts);
localStorage.setItem("isPostId", stringifyed);
console.log('heu',filtered)
}, [])
useEffect(() => {
// options a - full replace
localStorage.setItem('isPostId', JSON.stringify(isPostId));
// option b - only add unique, don't remove previous
var currentIds = JSON.parse(localStorage.getItem('isPostId')) || [];
isPostId.map((e) => {
if (!currentIds.includes(e) {
currentIds.push(e);
}
})
localStorage.setItem('isPostId', JSON.stringify(currentIds));
}, [isPostId])
Right now the code in the first if statement will put ONE id in local storage if there isn't one already, but not as an array. The code in the second if statement will also only set one id. You need to be setting an array value as shown below
If isPostId is declared as an array:
useEffect(() => {
// options a - full replace
localStorage.setItem('isPostId', JSON.stringify(isPostId));
// option b - only add unique, don't remove previous
var currentIds = JSON.parse(localStorage.getItem('isPostId')) || [];
isPostId.map((e) => {
if (!currentIds.includes(e) {
currentIds.push(e);
}
})
localStorage.setItem('isPostId', JSON.stringify(currentIds));
}, [isPostId])
If isPostId is declared as a string:
If you are certain there will not be single string values in localStorage and there will only be null values or arrays, you can do this as such:
useEffect(() => {
var currentIds = JSON.parse(localStorage.getItem('isPostId')) || [];
if (!currentIds.includes(isPostId) {
currentIds.push(isPostId);
}
localStorage.setItem('isPostId', JSON.stringify(currentIds));
}, [isPostId])
If there is a possibility that there could be individual string values, you will need an additional check for the code inside the useEffect
var currentIds = JSON.parse(localStorage.getItem('isPostId'));
if (!currentIds?.length) {
currentIds = [];
} else if (typeof currentIds !== 'object') {
// value in localStorage is a single string/number rather than an array
currentIds = [currentIds]
);
if (!currentIds.includes(isPostId) {
currentIds.push(isPostId);
}
localStorage.setItem('isPostId', JSON.stringify(currentIds));
Could simplify the second chunk further if desired
If I understood the question correctly, then you need something like this solution.
useEffect(() => {
const storageKey = "isPostId";
const json = localStorage.getItem("isPostId");
const previousPosts = json ? JSON.parse(json) : [];
const updatedPosts = [...previousPosts, ...isPostId];
const uniquePosts = Array.from(new Set(updatedPosts))
const stringifyed = JSON.stringify(uniquePosts);
localStorage.setItem("isPostId", stringifyed);
}, [])

Remove rows from state array

I have created a dynamic form which can have rows added and removed and are stored in a state array.
I need to remove the index passed into the function from the array, without storing a null or empty value.
This is my current code for removing the rows however this simply removes the last row and not the one required at index
const removeRow = (index) => {
setLocationRows((current) =>
current.filter((employee, i) => {
return index !== i;
})
);
};
This code removes the required index however sets the value to null / empty which messes up when after removing and adding rows.
setLocationsObj((current) => {
const copy = { ...current };
delete copy[index];
return copy;
});
Joe.
Im supposing you have something like this:
const [locationRows, setLocationRows] = useState([]);
const removeRow = (index) => {
setLocationRows(locationRows.filter((e,i)=> i !== index))
};
If so, try the above code.
For the complete CRUD operation you can use the following:
const addRow = (newRow) => {
setLocationRows([... locationRows, newRow])
};
const updateRow = (rowData) => {
setLocationRows(locationRows.map(e => {
if(e.id === rowData.id) return rowData;
else return e;
});
};
I hope this can help you!
I recently had to do something very similar and used the array splice method, as it allows you to remove the element at a specific index.
const removeRow = (index) => {
setLocationRows((rows) =>
// create deep copy
const newRows = JSON.parse(JSON.stringfy(rows));
// remove 1 element at index
newRows.splice(index, 1);
return newRows;
);
};
If you are dealing with any sort of nested array it's important to create a deep copy of that array, as the const copy = [...rows] method only creates a shallow copy and can cause all sorts of bugs when trying to manipulate the data further.
Hope this helps!

Javascript - Add Item in the middle of an array inside map function

I'm trying to add an item in a specific index inside an array inside a map function and it's been behaving unexpectedly. Here's the code for it
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'',
}
data.map(section=>{
section.content.map((report, reportIndex)=>{
if(report.id===uniqueID){
section.content.splice(reportIndex, 0, reportObject);
}
return report;
})
return section;
})
}
Here's a working pen - https://codepen.io/raufabr/pen/vYZYgOV?editors=0011
Expected behaviour is that it would insert an object in the specific index, right above the object where the ID matches.
However, it's acting weirdly and sometimes I'm getting 2 items being added instead of one.
Any tip on what I'm doing would be massively appreciated! I know I'm close but I've been stuck on this for a while now and can't figure out what I'm doing wrong!
Preface: You're using map incorrectly. If you're not using the array that map builds and returns, there's no reason to use it; just use a loop or forEach. More in my post here. And one reason to use an old-fashioned for loop is that you're in control of iteration, which matters because...
However, it's acting weirdly and sometimes I'm getting 2 items being added instead of one.
That's because you're inserting into the array being looped by the map, so on the next pass, it picks up the entry you're adding.
If you do a simple loop, you can easily avoid that by incrementing the index when you insert, or by looping backward; here's the looping backward approach:
const addItemToLevelTwoArray = (uniqueID, arrayID) => {
const reportObject = {
id: arrayID,
title: "",
};
for (const section of data) {
for (let reportIndex = section.content.length - 1; reportIndex >= 0; --reportIndex) {
const report = section.content[reportIndex];
if (report.id === uniqueID) {
section.content.splice(reportIndex, 0, reportObject);
}
}
}
};
Because we're looping backward, we won't pick up the entry we just added on the next pass.
Since the outer loop doesn't have that problem, I used the more convenient for-of.
Since you asked about map, if you do use the array map returns, you can do this by returning an array with the two entries, and then calling flat on the array map builds. (This only works if the array doesn't already contain arrays, because they'll get flattened to.) This is common enough that it's combined in one function: flatMap. It's not what I'd do (I'd do a loop), but it's certainly feasible. Sticking with forEach and flatMap rather than using for-of and for:
const addItemToLevelTwoArray = (uniqueID, arrayID) => {
const reportObject = {
id: arrayID,
title: "",
}
data.forEach(section => {
section.content = section.content.flatMap(report => {
if (report.id === uniqueID) {
// Return the new one and the old one
return [reportObject, report];
}
// Return just the old one
return report;
});
});
};
That assumes it's okay to modify the section object. If it isn't, Alberto Sinigaglia's answer shows creating a new replacement object instead, which is handy in some sitautions.
You can just use flatMap:
const data = [
{
content: [
{
id: 1,
title: "a"
},{
id: 3,
title: "c"
},
]
}
]
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'',
}
return data.map(section=> {
return {
...section,
content: section.content.flatMap( report =>
report.id === uniqueID
? [reportObject, report]
: report
)
}
}
)
}
console.log(addItemToLevelTwoArray(3, 2))
The following will extend the inner array .contentwithout modifying the original array data:
const data = [ {id: 0,title:'main',content:[{id:1,title:'Hello'},
{id:2,title:"World"}] } ];
const addItemToLevelTwoArray= (uniqueID, arrayID )=> {
const reportObject = {
id:arrayID,
title:'something new!',
}
return data.map(d=>(
{...d, content:d.content.reduce((acc, rep)=>{
if(rep.id===uniqueID) acc.push(reportObject);
acc.push(rep)
return acc;
},[]) // end of .reduce()
})); // end of .map()
}
const res=addItemToLevelTwoArray(1,123);
console.log(res);

Can I use .includes() to determines whether an array includes a certain element based on an array of strings instead of a single string?

Right now if I use this snippet of code, I get all elements whose region property is "Demacia"
let filtered = this.cards.filter((card) => {
return card.region.includes("Demacia");
})
Now I want to be able to get all elements whose property region is either "Noxus" or "Demacia", however, this doesn't seem to work as it returns an empty array
let regions = ["Demacia", "Noxus"];
let filtered = this.cards.filter((card) => {
return card.region.includes(regions);
})
Can I even do that or do I need to look into other array functions?
Instead of trying to pass multiple options to includes, look inside regions to see if it contains the region of the current card
let regions = ["Demacia", "Noxus"];
let filtered = this.cards.filter((card) => {
return regions.includes(card.region);
})
Just adding my answer because Array.prototype.includes() is not supported in IE, so if you want to support old browser, you can do
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Browser_compatibility
let regions = ["Demacia", "Noxus"];
let filtered = this.cards.filter((card) => {
return regions.indexOf(card.region) > -1;
})

Filter/Reject Array of strings against multiple values using underscore

I'd like to _.filter or _.reject the cities array using the filters array using underscore.
var cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou', 'China/Beijing', 'China/Baotou', 'China/Hohhot' ... ]
var filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];
My progress so far:
var filterList;
if (reject) {
filterList = angular.copy(cities);
_.each(filters, (filter) => {
filterList = _.reject(filterList, (city) => city.indexOf(filter) !== -1);
});
} else {
filterList = [];
_.each(filters, (filter) => {
filterList.push(_.filter(cities, (city) => city.indexOf(filter) !== -1));
});
}
filterList = _.flatten(filterList);
return filterList;
I'd like to DRY this up and use a more functional approach to achieve this if possible?
A somewhat more functional version using Underscore might look like this:
const cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany',
'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou',
'China/Beijing', 'China/Baotou', 'China/Hohhot']
const filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];
var inList = names => value => _.any(names, name => value.indexOf(name) > -1);
_.filter(cities, inList(filters));
//=> ["USA/Akron", "USA/Albuquerque", "China/Fuzhou", "China/Baotou"]
_.reject(cities, inList(filters));
//=> ["USA/Aberdeen", "USA/Abilene", "USA/Albany",
// "China/Guangzhou", "China/Beijing", "China/Hohhot"]
I'm using vanilla JavaScript here (some() and filter()) but I hope you get the idea:
const isValidCity = city => filters.some(filter => city.indexOf(filter) > -1)
const filteredCities = cities.filter(isValidCity)
Please note that this is a loop over a loop. So the time complexity is O(n * m) here.
In your example all city keys share the same pattern: country + / + city. Your filters are all an exact match to the city part of these names.
If this is a certainty in your data (which it probably isn't...), you could reduce the number of loops your code makes by creating a Map or object that stores each city per filter entry:
Create an object with an entry for each city name
Make the key the part that you want the filter to match
Make the value the original name
Loop through the filters and return the name at each key.
This approach always requires one loop through the data and one loop through the filters. For small array sizes, you won't notice a performance difference. When one of the arrays has length 1, you'll also not notice any differences.
Again, note that this only works if there's a constant relation between your filters and cities.
var cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou', 'China/Beijing', 'China/Baotou', 'China/Hohhot' ]
var filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];
const makeMap = (arr, getKey) => arr.reduce(
(map, x) => Object.assign(map, {
[getKey(x)]: x
}), {}
);
const getProp = obj => k => obj[k];
const getKeys = (obj, keys) => keys.map(getProp(obj));
// Takes the part after the "/"
const cityKey = c => c.match(/\/(.*)/)[1];
const cityMap = makeMap(cities, cityKey);
const results = getKeys(cityMap, filters);
console.log(results);
Since you seem to be using AngularJS, you could utilize the built-in filter functionality. Assuming both the cities and filters array exist on your controller and you're displaying the cities array using ng-repeat, you could have something like this on your controller:
function cityFilter(city) {
var cityName = city.split('/')[1];
if (reject) {
return filters.indexOf(cityName) === -1;
} else {
return filters.indexOf(cityName) > -1;
}
}
And then in your template, you'd do something like this:
<div ng-repeat="city in cities | filter : cityFilter"></div>
Of course you'd have to modify your syntax a bit depending on your code style (for example, whether you use $scope or controllerAs).

Categories