javascriptreactjsreact-hooks

React useState instantly calling the function I store in it as variable on re-render?


I am trying to make a filehandling system where you can delete the documents from the list of documents with the Delete button next to them. Upon clicking it, the usual pop-up window appears, asking for confirmation, and once clicking the Delete button there too, the file should be deleted. Problem is, that's not what's happening. The file is getting deleted right when the first Delete button is getting clicked. It would seem the useState in the App.js to which I pass the method that does the deleting calls it instantly on the re-render for some reason?

In the DocumentList.js:

const deleteSetter = () => {
        
    const deleteFile = () => {
        
        handleClick(path, fileName)
    }
        
    deleteButtonSetter(deleteFile)
    deleteFileSetter(fileName)
    deleteWindowSetter(true)
}
    
return (
    
    ...
    
    <div className="document-buttons">
    
        ...
    
        <ButtonWithIcon
            index={1}
            imageComponent={<DeleteIcon/>}
            onClickFunction={deleteSetter}
        />
    
    </div>
)

In the App.js:

const [isDelete, setDelete] = useState(false)
const [deleteFile, setDeleteFile] = useState(null)
const [deleteButton, setDeleteButton] = useState(null)
    
return (
        
    <div className="App">
        
        ...
        
        {isDelete ?
            (
                <DialogueWindow
        
                    elements={
        
                        <FileDeleter
                            fileName={deleteFile}
                            deleteButtonSetter={deleteButton}
                            closeButtonSetter={setDelete}
                        />
                    }
                />
            ) :
            null
        }
        
    </div>
)

In the FileDeleter.js:

return (
    
    <div className="button-container">
    
        <DialogueButton
            text="Delete"
            isAvailable={fileName ? true : false}
            onClickFunction={() => {
                            
                deleteButtonSetter()
            }}
        />
    
        ...
    
    </div>
)

What could be the issue? At no point do I use () after the name of the function, except when I finally call it in the FileDeleter's onClick event. So why does it get called right at the useState update?


Solution

  • The problem is that useState is used to store functions. Due to how React state updates work, it's necessary for state setter to accept callbacks to provide access to up-to-date state, and it cannot distinguish a callback from state value of type function.

    A function needs to be wrapped with an object in order for this to work, but it's likely that the state doesn't need to be reactive, so useState can be replaced with useRef:

    const deleteFile = useRef(null);
    

    If there is needed a setter at some point, it can be:

    const setDeleteFile = useCallback(value => deleteFile.current = value, []);