Why must I pass a function to the setter function of 'useState' in React?

I’m handling file uploads using react-dropzone, which calls the following function with a list of files:

    const [files, setFiles] = useState([])

    const onDrop = useCallback(async acceptedFiles => {
        uploadFiles(acceptedFiles)
    }, [])

I then upload the files using fetch:

    const uploadFiles = async acceptedFiles => {
        acceptedFiles.forEach(async (file) => {
            setFiles(array => [...array, fileState])

            await fetch('/upload', {
                method: 'POST',
                body: data
            }).then(res => {
                // etc
            })
        })
    }

If I replace the setFiles call with just setFiles([...files, fileState]) (not in a function) it breaks, but why is this?

79 thoughts on “Why must I pass a function to the setter function of 'useState' in React?”

  1. I believe I was able to reproduce the issue you’re having. https://codesandbox.io/s/elegant-bouman-9lb5k?file=/src/App.js

    Here is the relevant code:

      const [files, setFiles] = useState(["file1", "file2"]);
    
      const uploadFiles = async (acceptedFiles) => {
        acceptedFiles.forEach(async (file) => {
          //setFiles([...files, file]);
          setFiles(array => [...array, file])
    
          await fetch("https://httpstat.us/200");
        });
      };
    
      const testFunc = async (acceptedFiles) => {
        uploadFiles(["file3", "file4"]);
      };
    

    If behaves as expected when called like the above, but if you swap the setFiles calls, only file4 will be appended.

    The issue appears to be related to React batching setState calls as they are made in close succession. This post provides the best answer I could find.

    So it appears that when you call your setFiles with the callback, React does not batch those requests and thus executes them one after the other.

    Reply

Leave a Comment