skip to Main Content

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


  1. Chosen as BEST ANSWER

    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.

    const inputListRef = useRef();
    inputListRef.current = inputList;
        
    function deleteInput(indexToDel) {
        let tmp = [...inputListRef.current];
        
        // Find object to delete
        for (let i = 0; i < tmp.length; i++) {
            if (tmp[i].id == indexToDel) {
                tmp.splice(i, 1);
                break;
            }
        }
        setInputList(tmp);
    }
    

  2. Approach 1: Using a regular function:
    
    function DeleteInput(indexToDel) {
        console.log("Inside");
        console.log(inputList);
        let tmp = [...inputList]; // Create a copy of inputList
        tmp.splice(indexToDel, 1);
        setInputList(tmp);
    }
    

    Approach 2: Using useCallback hook:

    const DeleteInput = useCallback((indexToDel) => {
        console.log("Inside");
        console.log(inputList);
        let tmp = [...inputList]; // Create a copy of inputList
        tmp.splice(indexToDel, 1);
        setInputList(tmp);
    },
    

    [inputList]);

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search