In my Application when ever i change the number i want run the UseEffect Hook and re-render the updated values in the array which is working, but one condition i don't want to run the use UseEffect Hook is when i click addRow function i want bank field should be added in the rows except the number
JsCode
const [number, setNumber] = useState(3);
const [data, setData] = useState([]);
const [insValue,setInstValue]=useState(90);
useEffect(() => {
const list = [];
for (let i = 1; i <= number; i++) {
let ins = instValue/number;
list.push({
insNo: i,
installmentAmount: ins,
chequeNumber: '',
});
}
setData(list);
}, [number]);
const addRow = () => {
setNumber((number) => number + 1);
console.log('Add item clicked');
data.push({
insNo: number + 1,
installmentAmount: '',
chequeNumber: ''
});
const list = [...data];
setData(list);
};
Html
<div onClick={addRow}>
ADD ROW
</div>
currently what it is happening is when i click on add row number get changed and useffect Runs and data get updated i need is blank field in the row on the last object of an array
Output Getting
[{"insNo":1,"installmentAmount":22.5,"chequeNumber":""},
{"insNo":2,"installmentAmount":22.5,"chequeNumber":""}
{"insNo":3,"installmentAmount":22.5,"chequeNumber":""}
{"insNo":4,"installmentAmount":22.5,"chequeNumber":""}]
Output i need on click of addRow
[{"insNo":1,"installmentAmount":30,"chequeNumber":""},
{"insNo":2,"installmentAmount":30,"chequeNumber":""}
{"insNo":3,"installmentAmount":30,"chequeNumber":""}
{"insNo":4,"installmentAmount":"","chequeNumber":""}]
It's hard to understand what you exactly want.
But according to your code, you can't get the desired result.
Because you increase number and add a new row with empty values. And useEffect updates your array data again. So the installmentAmount in the last row will be filled.
To avoid it, you can change the code block like the following:
const initialNumber = 3; // Define initial value to check if data array is in the initial step.
const [number, setNumber] = useState(initialNumber);
// ... ...
useEffect(() => {
const list = [];
for (let i = 1; i <= number; i++) {
list.push({
insNo: i,
installmentAmount: i > initialNumber && i == number? '' : instValue/number,
chequeNumber: '',
});
}
setData(list);
}, [number]);
const addRow = () => {
setNumber((number) => number + 1);
console.log('Add item clicked');
const list = [...data];
list.push({
insNo: number + 1,
installmentAmount: '',
chequeNumber: ''
});
setData(list);
};
The data is refreshed again into the useEffect when you change number, losing all data.
You can avoid change number state at click, since the new data is already being saved manually.
setNumber((number) => number + 1);
Related
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);
}, [])
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.
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!
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
I want to make a call the database (firestore) to return the data of the products in real time that the user has added them to the cart then this data will be used in a code to get the total price of the cart items so I have tried the following approach:
This useEfect will get make the database call and will set the items state to to an array of objects that contains the id and quantity of each cart item on real time
const [items, setItems] = useState([]);
const [subTotal, setSubTotal] = useState([]);
const [Total, setTotal] = useState(0);
const oneSubTotal = [];
useEffect(() => {
db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket").onSnapshot((docs) => {
let array = []
docs.forEach(doc =>{
array.push(doc.data())
console.log(array)
setItems(array)
})
});
}, [])
This code should loop through the items array to add an the element item.price to each object after getting the price of each item by another call to the database then it push to the Subtotal array the total price of each item by pushing the quantity multiplied by the price
useEffect(() => {
items && items.forEach((item) => {
// console.log(item)
const id = item.id
db.collection("products").doc(id).get().then((e)=>{
item.price = (e.data().price)
oneSubTotal.push(item.price * item.quantity)
setSubTotal(oneSubTotal)
})
})
then this code will loop through the subtotal array to get the sum of the price of the items
let sum = 0;
for (let num of subTotal){
sum = sum + num
}
useEffect(() => {
setTotal(sum)
}, [sum, items])
but the issue is the value of Total when the page renders first time always will be the total price of the first item which is represented with the first object in the items array and when I modify the quantity of any item (without refreshing the page) the Total value shows the correct amount for few seconds then the value of it returns to show the first items total price only
In your onSnapshot handler, you are calling console.log(array) and setItems(array) prematurely. This potentially causes your app to be rendered multiple times. You should make sure to be calling these lines outside of the forEach loop.
.onSnapshot((docs) => {
let array = []
docs.forEach(doc => {
array.push(doc.data())
});
console.log(array)
setItems(array)
});
But even so, it would be more efficient to fetch the item prices before calling setItems. Plus, instead of calling out to the database one-by-one using forEach, you should bundle the requests into batches like shown in this answer which is available as a utility function, fetchDocumentsWithId().
.onSnapshot((docs) => {
const cartItems = [], itemPriceObject = {};
// cartItems will be ({ id: string, quantity: number, price: number | null, lineTotal: number })[]
// itemPriceObject will be a map of IDs to their price (a Record<string, number | null>) (used to deduplicate IDs & store prices)
docs.forEach(doc => {
const cartItem = doc.data()
cartItems.push(cartItem)
itemPriceObject[cartItem.id] = null
});
// idsArray is a deduplicated list of IDs
const idsArray = Object.keys(itemPriceObject);
fetchDocumentsWithId(
db.collection("products"),
idsArray,
(itemDoc) => {
itemPriceObject[itemDoc.id] = itemDoc.get("price") // more efficient than itemDoc.data().price
}
)
.then(() => {
// if here, all prices (that are available) have been retrieved
// MAY BE NULL! Consider these items to be "not available"
const totalSum = 0
// put prices in their items, calculate line cost and add to total
cartItems.forEach(item => {
item.price = itemPriceObject[item.id]
item.lineTotal = item.price === null ? 0 : item.price * item.quantity
totalSum += item.lineTotal
}
// set items & total sum
setItems(cartItems)
setTotal(totalSum)
})
.catch((error) => {
// failed to retrieve some documents from the database
// TODO: update UI
});
});
Note: For clarity, subTotal (meaning: the sum of some, but not all values) was renamed to lineTotal (meaning: the cost of items in this entry/line, the cost x quantity)