I’m making a web page with a dynamic list of form inputs, where more input fields can be added or deleted using buttons. I have a parent object <Ingredients />
that keeps track of the inputs and keeps them in some react state as a list of child components, <Ingredient />
. The <Ingredient />
children each have a delete button to remove them from the list, and are passed a function callback, DeleteInput
, as a prop for the parent to delete them when clicked.
The issue is that when pressing the delete button, all the fields after that input dissapear too. It seems to be because when the callback is called, the value of inputList
inside the function is whatever value it was when the component was first created. I suspect the DeleteInput
function is being part of the closure for the child component, even though it lives in the parent?
The output from the print statements shown in the image highlights this. Even though there are 3 input forms, the inner function only sees the first one because that was the state of inputList
when it was created.
How do I fix this bug so that the value of inputList
being used inside the DeleteInput
function is the most current state? Ive tried using the react useCallback hook, with the inputList
as a dependency, but it had the same issue.
// Parent
export function Ingredients() {
const [inputList, setInputList] = useState([]);
const [key, setKey] = useState(1);
function AddInput() {
setInputList(inputList.concat([<Ingredient
key={key}
index={key}
deleteInput={deleteInput}
/>]))
setKey(key+1);
}
// Approach 1: Using a normal regular function
function DeleteInput() {
console.log("Inside");
console.log(inputList);
let tmp = inputList;
tmp.splice(indexToDel, 1)
setInputList(tmp);
}
// Approach 2: Using a useCallback hook
const DeleteInput = useCallback((indexToDel) => {
console.log("Inside");
console.log(inputList);
let tmp = inputList;
tmp.splice(indexToDel, 1)
setInputList(tmp);
}, [inputList])
console.log("Outside");
console.log(inputList);
return (
<div>
{inputList}
<button type='button' onClick={AddInput}>Add More</button>
</div>
)
}
// Child
function Ingredient({index, deleteInput}) {
const i = index;
return (
<div className={styles.ingredient}>
<input className={styles.ingredientname} type='text' placeholder='Ingredient'></input>
<button type='button' onClick={() => DeleteInput(index)}>X</button>
</div>
)
}
print statement outputs when 3 inputs are added, and the 2nd one is deleted
2
Answers
I found the solution was to use the react useRef hook to keep a reference of the state. I then update this reference on every render, and it works as intended inside the delete function.
Approach 2: Using useCallback hook:
[inputList]);