0

I am getting a book list from database and is stored in a state variable Book list also has book price field

const [books, setBooks]=useState([])
setBooks(data)

Each books object in the array has properties like BookName, Author , Price, Discount

I have rendered a html table like follows

return ( <div>
{books.map((x,i) => ( <tr>
                         <td>x.bookName</td>
                         <td>x.price</td>
                         <td><MyCustomTextInput onChange={e => handleChange(e, x.id)}  value={x.discount}></MyCustomTextInput></td>
                      <tr></div>);

the sample code for MyCustomTextInput is as follows

function MyCustomTextInput(props)
     { return (<div><TextInput></TextInput> </div>) 
     } exports default MyCustomTextInput

The code where I update the price for corresponding object in "books" array is as follows

function handleChange(x,id){
  var obj = books[id];
  obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
}

Every thing works properly except the price after discount is not reflecting on the UI. though its getting updated properly in the array but not reflecting on the UI.

any help....

Experts -

2
  • The handleChange function isn't updating state at all. Did you just forget to call setBooks with the new state? Commented Aug 20, 2021 at 14:34
  • @david Its an arrray, I am just updating the price for corresponding book which has changed. I am not reassigning the array again. Could you please share your thoughts on how do I do it? Commented Aug 20, 2021 at 14:35

3 Answers 3

3

You need to setBooks to update state books.

function handleChange(x, id) {
  setBooks(
    books.map((item) =>
      item.id === id ? { ...item, price: item.price - parseFloat(e.target.value) } : item,
    ),
  );
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Viet. Appreciate your help.
2

This is setting a value:

function handleChange(x, id){
  var obj = books[id];
  obj.price = obj.price - e.target.value;
}

But it's not updating state. The main rule to follow here is to never mutate state directly. The result of bending that rule here is that this update never tells React to re-render the component. And any re-render triggered anywhere else is going to clobber the value you updated here since state was never updated.

You need to call setBooks to update the state. For example:

function handleChange(x, id){
  setBooks(books.map(b =>
    b.id === id ? { ...b, price: b.price - parseFloat(e.target.value) } : b
  ));
}

What's essentially happening here is that you take the existing array in state and use .map() to project it to a new array. In that projection you look for the record with the matching id and create the new version of that record, all other records are projected as-is. This new array (the result of .map()) is then set as the new updated state.

There are of course other ways to construct the new array. You could grab the array element and update it the way you already are and then combine it with a spread of the result of a .filter to build the new array. Any construction which makes sense to you would work fine. The main point is that you need to construct a new array and set that as the new state value.

This will trigger React to re-render the component and the newly updated state will be reflected in the render.

5 Comments

Thank you David, I tried to implement this logic but it's actually clearing the book array entirely. The same thing is happening when I used the code logic provided by "Amer Yousuf"
@VR: When you debug, when handleChange is invoked, what is the value of books at that time? (You can do console.log(books) within handleChange and observe the result on the browser's console.) If it's en empty array then your state is an empty array and was never populated with anything. The only place in the question where you currently populate it is: setBooks(data), but what is data? Where does it come from?
I tried debug, before value is having entries in the array. As soon as code comes in HandleChange and executes the code logic provided above, it clears all the objects inside the array. must be some issue .....
@VR: Since the code in the question wasn't udpating state properly, it seems likely that you have other code which is not updating state properly. At this point the bottom line is that if the state is an empty array then what you have is an empty array. This code doesn't "clear all the objects inside the array" because there weren't any objects there to begin with. You have most likely made a mistake elsewhere in the code, outside of what's seen in the question above.
yes that may be correct. But I really appreciate the way you have explained it to me. I will figure out the reason for empty array. I am going to accept your answer. Could you please also let me know if this is a good approach mentioned below? 1. Keep the original array list 2. Create an entire duplicate list (without reference to original) 3. Modify the duplicate list by updating one object matching to the condition 4. Assign the duplicate to the original list again using setBooks(modifiedList)
1

To achieve that, you need to call setBooks after changing the price within handleChange method to re-render the component with the newly updated state.

It's simply like the following:

function handleChange(x,id){
  var obj = books[id];
  obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
  setBooks([...books]);
}

2 Comments

Thank you Amer, but this code logic is clearing all my entries in books array.
there must be some other logical issue in the code that I will check. But the sample code you have provided is really helpful. Thank you again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.