I am displaying table from API, so when I click to delete it should delete
Now it's deleting actually. but the problem is it's not rendering the output
Here is my code for that function
const delpersonHandler = index => {
const apiDel = api;
console.log(index);
api.splice(index, 1);
console.log(api);
setApi({ ...api, apiDel });
};
here is where i call that
<TableCell align="Justify">
<Button variant="outlined">Edit</Button>
<Button variant="outlined" onClick={() => delpersonHandler(id)}>
Delete
</Button>
</TableCell>
full code is available here
https://pastebin.com/u7fAefBH
React states are immutable hence doing api.splice(index, 1); does not work because you're directly affecting the React state.
api is an array but you're setting an object in the setApi setter function
the simplest way to do this is
const delpersonHandler = index => {
let oldApi = [...api] // new array containing api's elements
oldApi.splice(index,1);
setApi(oldApi);
};
Assuming you are working with functional components, the useState hook delivers two values, specifically the getter and setter of your state, invoking the last one with some parameter will set a new value and invoke a render call.
In the example, setApi is this setter method, but you are calling it with an object as parameter instead an array. And by using the splice method with the api value is possible to inferer that it must be an array. So you need to call the setter with the same variable type:
const delpersonHandler = index => {
// Don't apply changes directly, instead clone it and modify it.
const newApi = api.slice();
newApi.splice(index, 1);
// Pass an array instead an object
setApi(newApi);
};
I found the fix by using the below code
const delpersonHandler = index => {
const apiDel= [...api]; //getting the current state of array using spread operator
apiDel.splice(index, 1); //deleting based on index
setApi(apiDel);
console.log("state updated", api);
}
[...api] without this line this wasn't working
Related
I am getting an object from parent component and setting to state. In child component I am updating the state, but the parent reference object values also changing instead of only state changes.
Parent Component has a huge object,
obj = {
values: {
per1: { name: "rsk" },
per2: { name: "ssk" },
}
}
Child Component:
const ChildComponent = ({obj}) => {
const [inp, setInp] = useState(obj.values);
const onChange = useCallback(({target}) => {
setInp((prev) => {
const nD = { ...prev };
//k1, k2 comes from some logic
nD[k1][k2] = target.value;
return nD;
})
}, []);
return (
Here the "inp" state is looped by objects and keys in text box to build a html form
)
}
Here the question is, why the core obj.values also changing on onChange setInp time. I dont want to disturb the obj.values untill i submit the form.
Because before submit the Form, I need to validate,
obj.values are equal or not to inp state values
Any idea on this.
The original object is changing because in JS, when you pass an array or an object in such a way, you are actually passing a reference to the original object/array.
Meaning that any changes made to the reference, will also affect the original.
In-order to avoid using the reference, you can copy the object/array and work with the copy instead.
There are a few ways of doing this, the simplest IMO is using the spread syntax.
Example:
const ChildComponent = ({obj}) => {
const [inp, setInp] = useState({...obj.values});
...
}
What we do here is "spread" the contents of obj.values into a new object, thus creating a new object and avoiding using the reference.
Note that the spread syntax only makes a shallow-copy, this isn't necessarily an issue unless you have some complex object which contains some other nested objects within it.
If you do need to perform a deep-copy, one simple way of doing it is via JSON methods.
Example:
const clone = JSON.parse(JSON.stringify(original));
First, this obj variable is incorrect because per1 is defined as object and object consist of key and value, but it is like a string array so please check that, and below is solution
In Child Component you should create a copy of that obj variable
const [inp, setInp] = useState(Object.assign({}, obj.values));
Bz if you set inp as the same variable it will pass the address of that variable so for that you need to create the copy and then you can change that inp.
Im programming a component where I am mapping over a component using an array I have stored in state.
const [animalList, setList] = useState(['cat', 'dog'])
{ animalList.map((tag) => {
return (
<AnimalButton animalz={tag}/>
)
})
}
I want to add to the state array to force to make the app rerender. I attempted to do so with a .push function, store the increased array in a temporary variable and assign that new array to the state, as push() and unsplice don't return the actual array.
onSubmit={(values, actions) => {
actions.resetForm();
animalList.push(values.pet)
let newList = animalList
setList(animalList = newList)
}}
However I get this error [TypeError: "animalList" is read-only]?
Is there a way to change add to animalList without my resulting list in the state becoming read only?
Yes you cannot push this into const.
However, you can use this approach.
setList([...animalList,values.pet])
I have a requirement where for each row of a table(rows are dynamically populated), I have a 'Details' button, which is supposed to pop up a modal upon clicking. How do I maintain a state for each of the rows of this table, so that React knows which row I am clicking, and hence pass the props accordingly to the modal component?
What I tried out was create an array of all false values, with length same as my data's. The plan is to update the boolean for a particular row when the button is clicked. However, I'm unable to execute the same.
Here's what I've tried so far:
let initTableState = new Array(data.length).fill(false)
const [tableState, setTableState] = useState(initTableState)
const detailsShow = (index) => {
setTableState(...tableState, tableState[index] = true)
}
I get the 'data' from DB. In the function detailsShow, I'm trying to somehow get the index of the row, so that I can update the corresponding state in 'tableState'
Also, here's what my code to put in modal component looks like, placed right after the row entries are made:
{tableState[index] && DetailModal}
Here DetailModal is the modal component. Any help in resolving this use case is greatly appreciated!
The tableState is a single array object. You are also spreading the array into the setTableState updater function, the state updater function also takes only a single state value argument or callback function to update state.
You can use a functional state update and map the previous state to the next state, using the mapped index to match and update the specific element's boolean value.
const detailsShow = (index) => {
setTableState(tableState => tableState.map((el, i) => i === index ? true : el))
}
If you don't want to map the previous state and prefer to shallow copy the array first and update the specific index:
const detailsShow = (index) => {
setTableState(tableState => {
const nextTableState = [...tableState];
nextTableState[index] = true;
return nextTableState;
})
}
I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}
I have a parent component "Checkout", where I call my api for the events tickets and save the data to state. I have a simple function that takes the existing ticket information and adds one to the ticket the user selected.
Checkout Component
const handleTicketAdd = (e) => {
// create new ticket array
const newTickets = tickets;
//
newTickets[e.target.id].count++;
console.log(newTickets);
setTickets(newTickets);
console.log(tickets);
}
This function is passed as a prop to the child component "Tickets" and is called in a mapped row.
The function works fine and is updating the count state in the console, but the child component is not re-rendering and the values on screen are staying at the inital value.
I have been researching and have found that componentWillReceiveProps has been replaced with the useEffect hook. I have been trying to get it to work in my child component without sucesss:
Tickets Component
useEffect(()=>{
console.log("Tickets Changed", props.tickets);
}, [props.tickets]);
The log doesn't fire when props.tickets changes, so I know that I am not handling this correctly. Can someone point me in the right direction.
Update
Based on the below feedback I have revised my code and it's almost complete. The problem is that it's adding a new field the array with the count value of the object instead of just updating the count field of the object.
setTickets(prevTickets => ([...prevTickets, prevTickets[e.target.id].count = prevTickets[e.target.id].count + 1])
)
If I try something like poping the last value, I get even more new fields added to the array. How would I achieve removing the additional field that's being generated. The following is not working:
setTickets(prevTickets => ([...prevTickets, prevTickets[e.target.id].count = prevTickets[e.target.id].count + 1], prevTickets.pop()),
)
setTickets(prevTickets => ([...prevTickets, prevTickets[e.target.id].count = prevTickets[e.target.id].count + 1], prevTickets.slice(-1)[0),
)
Alright, so it looks like the way to edit your array's in state should be done with a map function or loop using the previous state. I was able to work it out based on Shubham's feedback.
// update count based on previous state
setTickets(prevTickets => (prevTickets.map((ticket, index) => {
console.log(index, e.target.id);
// if mapped object's id equals the arrays index, it's the value to edit
if (e.target.id == index) {
console.log(index);
const count = ticket.count + 1;
// return object with updated count values
return { ...ticket, count }
}
return ticket;
})
))
This gives me an edit of the object value that I want in my array without any additional fields or values.
You shouldn't be mutating the state while updating, instead you need to clone and update the state
const handleTicketAdd = (e) => {
const id = e.target.id;
setTickets(prevTickets => ([
...prevTickets.slice(0, id),
{
...prevTickets[id],
count: prevTickets[id].count + 1
}
...prevTickets.slice(id + 1);
]));
}