Managing state of individual rows in react js table - reactjs

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;
})
}

Related

Trying to reset a list before filtering, but having trouble with the asynchronous states

So i have a const with an array of objects, named "data", a state that receive that data so I can update the screen when I change it, and I have a function that filter that data.
Every time that the user change the state of an HTML select, I trigger a function named handleChange that calls the filter function, so the user user can see the filtered content.
The problem is that I want to reset the state that receives the data, with the original data before filtering it, so if the user change one of the selects, it filter based on the original data, not the previous changed one, but when I try to update the state with the const data value, it doesn't work.
Here is my code
const [list, setList] = useState<IData[]>([...data]);
const [filter, setFilter] = useState<IFilter>({
name: "",
color: "",
date: "",
});
function handleChange(
key: keyof IData,
event: React.ChangeEvent<{ value: string }>
): void {
const newFilter = { ...filter };
newFilter[key as keyof IData] = event.target.value;
setList([...data]); // reset the data
setFilter({ ...newFilter }); // set the filter value
filterList();
}
function filterList(): void {
const keys = Object.keys(filter) as Array<keyof IData>;
keys.forEach((key: keyof IData) => {
if (filter[key]) {
const newList = list.filter((item: IData) => item[key] === filter[key]);
setList([...newList]);
}
});
}
the problem is here
setList([...data]); // reset the data
setFilter({ ...newFilter }); // set the filter value
filterList();
apparently, when the filterList happens, the list state is not yet restarted with the data value, since if I console log it inside filterList(), it return me only the list that was previous filtered. Is there a way for me to make sure that the setList happens before filtering, so I'm sure that the list that is filtered is always based on the initial value?
You can access the updated values inside useEffect (more about useEffect), so instead of calling filterList() directly inside the handler you move it inside the hook:
React.useEffect(()=>{filterList();},[list,filter])
Alternatively, you can pass the values as parameters to the function, after you updated the state with them
filterList(newList, newFilter)
UPDATE
As you update list inside filterList you'll need some sort of a flag to indicate if you need to filter of not (I'd use useRef). Note that it would be preferable to pass parameters into filterList instead, because this way there will be one less rendering cycle. Here is a simplified example of how both can work, let me know if they make sense : https://jsfiddle.net/6xoeqkr3/

Delete item from list not rendering react js

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

Correct way to use useEffect to update Child Component after replacing state array

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

Why does array state item using hooks only updates component when using spread?

I don't understand why a component only re-renders when using spread operator in an array state item as follows:
I first create a dynamic array state with false values:
const [openList, setOpenList] = React.useState(props.navBarItems.map((item, index) => false));
Option 1
function handleClick(index) {
let newOpenList = [...openList]
newOpenList[index] = !openList[index]
console.log(newOpenList) // prints the same in both options
setOpenList(newOpenList);
}
Option 2:
function handleClick(index) {
let newOpenList = openList
newOpenList[index] = !openList[index]
console.log(newOpenList) // prints the same in both options
setOpenList(newOpenList);
}
I used a console.log in render() (there's probably a better way to know if component updated using hooks) and it is only called when using option 1.
Because on option 2 you are mutating the openList array. When you do let newOpenList = openList you are not creating a new variable, you are just assigning openList to newOpenList. Even after adding one more item to newOpenList, when react compare the oldOpenList === openList this return true, and react does not update.
On option 1, when you do [...openList] it creates a new array with the same items of openList. As it will be a new variable, when react compares oldOpenList === openList it returns false, and react update.
You can have a more completed answer about that here

How to store props passed on Click of an event in React

I recently started learning React, I have a data grid in my component, On click of a Row an event is triggered. In an event handler I could fetch the Row props (values from each column of a row clicked). So what I wanted to achieve is: I have 3 buttons on Form Component which gets enabled onClick of Row in the Grid. I want to navigate to new page onClick of each button wherein I will be performing different actions for the Row selected in a grid. The problem which I am facing is, How can I pass Row props to the handler of all 3 buttons as I need the Row values to perform different operations using these buttons.
onRowclick = (row) =>
{
this.context.route.push(`path\${row.ID}`);
}
onButton1click = (row) =>
{
this.context.route.push(`path\${row.ID}`);
}
.
.
.
onButton3click = (row) =>
{
this.context.route.push(`path\${row.ID}`);
}
I need the row.ID in all three event handlers, is their some way to store this in a state so that I will be able to use this throughout the component.?
UPDATE: Below is the code to achieve this
constructor(props) {
super(props);
this.state = this.getInitState(props);
this.intitialize();
}
intitialize(){
this.selectedRow = {};
}
getInitState(props) {
return {selectedRow: undefined};
}
// on Row click storing the row object(which contains row data)
onRowClick = (row) => {
this.selectedRow = row;
}
very basic of React
I think it's time to use REDUX.
Redux is a react framework and is used to store state in an object. Redux makes the tree of objects and you can retrieve any state through the application by using the reducer and storing state with the help of action.
Redux have a store where it stores all the state of the application.
To know full detail about redux please read this: http://redux.js.org/

Resources