Add data into Array in local storage, React - javascript

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);
}, [])

Related

.filter() over array works only the first time

in react-native, I am trying to add a simple filtering option on the top of my screen. Just like this one.
But the filter works only on the first hit. After the first, the new array resolves always as empty.
Could anyone tell me where/why is this code failing? Thanks a lot!
import { exercisesList } from '-utils/exercisesList'
const [items, setItems] = useState(exercisesList)
const handleFilter = (treatment = 'All') => {
console.log('FILTER-TREATMENTE---->', treatment)
let filteredList = exercisesList
if (treatment === 'All') {
setItems(exercisesList)
} else {
filteredList = items.filter((item) => item.treatment === treatment)
console.log('filteredList----->', filteredList)
setItems(filteredList)
}
}
I think it is because the second time that the function runs the items has the previous filteted list, not the full list and you are filtering the items array, not exercistsList
I know others answers solve the problem but I think we can expand on the issue a bit just to better understand what went wrong. It was happening because the filtering was being run directly on the state it was supposed to alter so when second run comes its running on previously filtered data that may or may not meet the filtering requirements. Some pseudo code below on how it should have been done
data -> filter(data) -> updateState(filteredData) -> repeat()
const handleFilter = (treatment = 'All') => {
console.log('FILTER-TREATMENTE---->', treatment);
let filteredList = [];
if (treatment === 'All') {
setItems(exercisesList);
} else {
filteredList = exercisesList.filter((item) => item.treatment === treatment);
console.log('filteredList----->', filteredList);
setItems(filteredList);
}
};

React Native: remove element from array if id is already present

in my react native app I have a list of tags where people can choose from, they click on an item and add it to the list of array of item ids, if they click over an item which id is already in the array, I want to remove it from array.
Right now all I can do is add ids to array, I can't remove it if already present.
PD: I also check if list of ids is lower than 10. Also, is there a cleanest way to write the function?
const [selectedItems, setSelectedItems] = useState([]);
const toggleItem = useCallback((itemId) =>
{
setSelectedItems(prev => prev.includes(itemId) ? prev.filter(obj => obj.id != itemId) : prev.length < 10 ? [ ...prev, itemId] : prev);
},[])
It looks like you're mixing and matching the items in the array - is it a list of IDs, or the list of objects?
prev.includes(itemId) // This looks like a list of IDs
? prev.filter(obj => obj.id != itemId) // This looks like a list of objects
In terms of "a cleaner way to write the function" - you can be a bit more efficient if you don't do the includes check first, as that involves an extra iteration over your items. In the example below I've changed the callback function to assume it gets passed the whole item, instead of just the id:
const [selectedItems, setSelectedItems] = useState([]);
const toggleItem = item => setSelectedItems(prev => {
const next = prev.filter(selectedItem => selectedItem.id !== item.id);
// The item wasn't removed from the list so it needs to be added
if (next.length === prev.length && next.length < 10) {
next.push(item);
}
return next;
});
You could also consider using a Map instead of an array:
const [selectedItems, setSelectedItems] = useState(new Map());
const toggleItem = item => setSelectedItems(prev => {
const next = new Map(prev);
if (!next.delete(item.id) && next.size < 10) {
next.set(item.id, item);
}
});
// If you need the items as an array
const selectedItemsArray = Array.from(selectedItems.values());
Finally - I'd also remove the useCallback because you probably don't need it.

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!

How to replace the object array using simple filter and spread operators

I need a suggestion for the below code. The objective here is to remove the specific array if the mentioned menuKey is present.
Note: sdb.menu = 'account-links' (Declared in another file);
const { menu = [] } = remainingConfig[sdb.menu]?.params || {};
const keysToRemove = ['sidebarReferAFriend'];
const filteredMenu = menu.filter(({ menuKey }: IMenuLink) => !keysToRemove.includes(menuKey));
How can I assign the filteredMenu back to remainingConfig object?
I tried with some spread operator options and it's not giving the existing same structure. So please provide some help here when you have some time.
The object structure will be like attached image.
If you can directly assign to the remainingConfig[sdb.menu].params.menu property, then since presumably you don't need to create the array if it's not there, only do the work if the array and everything leading up to it exists, then just assign back to menu:
const menuConfigParams = remainingConfig[sdb.menu]?.params;
const menu = menuConfigParams?.menu;
if (menu) {
const keysToRemove = ['sidebarReferAFriend'];
menuConfigParams.menu = menu.filter(({ menuKey }/*: IMenuLink*/) => !keysToRemove.includes(menuKey));
}
If the remainingConfig structure is deeply immutable, then we have to create a new object to replace it at every level of the nesting:
const menuConfig = remainingConfig[sdb.menu];
const menuConfigParams = menuConfig?.params;
let menu = menuConfigParams?.menu;
if (menu) {
const keysToRemove = ['sidebarReferAFriend'];
menu = menu.filter(({ menuKey }/*: IMenuLink*/) => !keysToRemove.includes(menuKey));
const newConfig = {
...remainingConfig,
[sdb.menu]: {
...menuConfig,
params: {
...menuConfig.params,
menu,
}
}
};
// ...then use whatever mechanism is in your environment to replace `remainingConfig`
// with `newConfig`...
}
Notice how at each level we're making a shallow copy of the structure via spread syntax.

Nested Object mutates in React

I am in almost desperate need of help. I am a mechanical engineer and I'm doing a type of calculator for my line of work. I have had an issue I've spent weeks on. I can't seem to solve it.
To not bore you with long code I will try to generalise it as much as possible.
I will first present an example code.
Then I will explain the expected behaviour and what is actually happening for me.
Finally I will explain what I have tried so far to solve this issue.
I will add more content at the bottom based on comments to help clarify my question.
CODE EXAMPLE
THE PARENT OBJECT
import {childObject} from "./childObject"
// in my code "childObject" are actually different from each other
const object1 = Object.assign({}, childObject);
const object2 = Object.assign({}, childObject);
const object3 = Object.assign({}, childObject);
const object4 = Object.assign({}, childObject);
const object5 = Object.assign({}, childObject);
const object6 = Object.assign({}, childObject);
const exampleObject = {
name: "foo",
otherInfo: "bar",
nestedObject:{
standardType: [object1, object2, object3],
specialType: [object4, object5, object6]
},
sumfunc(){}
}
THE CHILD OBJECT
export const childObject = {
name: "I'm losing my mind",
value: "" //<-- this will change
otherInfo: "please help me",
sumfunc(){}
}
EXPLAINING
What I am doing is the following:
Searchbar with all types of parentObjects.
Allowing user to select one or multiple of same or different parentObjects.
Storing the copied selection in a redux store.
Displaying the selection, each parentObject as a form. [see picture]
When typing in form the value of the nested object will change
Now... The issue is when I open the searchbar and select the same parentObject, thus copying it, all its values are mutated. As seen in picture above.
WHAT I HAVE TRIED
I have tried to use lodash clone and deepClone on the selected parentObject.
I have tried to use loads clone and deepClone on the selected childObjects.
I have tried, since the object have the same structure, to go through all key value pairs and shallow copy them.
I have tried to not send the parentObject via the searchbar component to the reducer, instead I just send a string and the reducer itself will add the parentObject to the store.
All methods that I've tried have not stopped the mutation. The deepClone method stopped the mutations, but in return the functions in the objects stopped working (maybe I need to bind it somehow?)
MORE CONTENT
The code that updates the value of the nestedObject
const inputsHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const formCopy = Object.assign({}, formEQ);
const inputFieldName = e.target.name;
// if anything other than a empty, number or decimal inputted, then return
const isNum = e.target.value.match(/^(?:\d{1,8}(?:\.\d{0,8})?)?$/);
if (!isNum) return;
// Update priority list to calculate the last updated input
formCopy.priorityList = formCopy.priorityList.sort((a, b) => {
if (a === inputFieldName) return 1;
if (b === inputFieldName) return -1;
else return 0;
});
// Update selected input field
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === inputFieldName) {
input.value = e.target.value;
}
return input;
});
// If more than two inputs empty do not calculate
const emptyInputs = formCopy.inputs[calcmode].reduce(
(acc, nV) => (nV.value === "" ? (acc += 1) : acc),
0
);
// Calculate the last edited input field
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === formCopy.priorityList[0] && emptyInputs <= 1) {
const calculatedValue = formCopy.calculate(
formCopy.priorityList[0],
calcmode
);
input.value = Number(calculatedValue).toFixed(2);
}
return input;
});
// Final set hook, now with calculated value
setformEQ({ ...formCopy });
};
Please good people of StackOverFlow... Help me!
Your code has few problems :
you are filtering based on name property of child object and all of them has the same name. Always provide unique id to the objects so that they can be differentiated in easy manner.
Your filter logic is so wrong :
formCopy.inputs[calcmode] = formCopy.inputs[calcmode].map((input) => {
if (input.name === inputFieldName) {
input.value = e.target.value; // < -- Culprit
}
return input;
});
Never mutate inline, always create a new copy.
This is how your code change function should be (I have removed dynamic key selection for clarity) :
const change = (e, id) => {
const inputFieldName = e.target.name;
// local copy of array
const nestedArr = [...qform.nestedObject.standardType];
// finding item to be updated
const index = nestedArr.findIndex((i) => i.id === id);
console.log({ index, inputFieldName, e, id });
if (index !== -1) {
const item = nestedArr[index];
item.value = e.target.value;
nestedArr[index] = item;
// deep copy till k depth where k is the key to be updated
const newObject = {
...qform,
nestedObject: {
...qform.nestedObject,
standardType: [...nestedArr],
},
};
setQform(newObject);
}}
Check this Example : Demo

Categories