skip to Main Content

I’ve code below. console.log from useEffect shows current "files" value, but console.log from handleInputChange function always show previous value. I understand this is because of async setState behavour, but I don’t understand how to make it work.

   import React, { useState, useEffect } from 'react';
    import Button from '../Button/Button';
    
    function Card({ accept }) {
      let [uploadDisabled, setUploadDisabled] = useState(true);
      let [multiple, setMultiple] = useState(true);
      let [files, setFiles] = useState([]);
    
      function handleOpenClick() {
        const inputEl = document.getElementById('file');
        inputEl.click();
      }
    
      useEffect(() => {
        console.log(files);
      });
    
      function handleInputChange(event) {
        event.preventDefault();
    
        if (!event.target.files) return;
    
        setUploadDisabled(false);
        setFiles(Array.from(event.target.files));
        console.log(files)
    
        files.forEach((file) => {
          const reader = new FileReader();
    
          reader.onload = (event) => {
            const src = event.target.result;
            const alt = file.name;
          };
        });
      }
    
      return (
        <div className='card'>
          <input
            type='file'
            id='file'
            multiple={multiple}
            onChange={(event) => handleInputChange(event)}
            accept={accept.join(',')}
          />
          <Button value='Open' className='btn' handleClick={handleOpenClick} />
          <Button value='Upload' className='btn primary' disabled={uploadDisabled} />
        </div>
      );
    }
    
    export default Card;

2

Answers


  1. Why you just use files from event params?

    setFiles(Array.from(event.target.files));
    

    you did it shown in the above code so you expect files to be equals to event.target.files and handle it in future with forEach

    files.forEach((file) => {
      const reader = new FileReader();
    
       reader.onload = (event) => {
         const src = event.target.result;
         const alt = file.name;
       };
    });
    

    So you can do

    const files = Array.from(event.target.files);
    
    files.forEach((file) => {
      const reader = new FileReader();
        
      reader.onload = (event) => {
        const src = event.target.result;
        const alt = file.name;
      };
    });
    
    setFiles(files);
    

    Another point is that you need array dependency in useEffect

    useEffect(() => {
      console.log(files);
    }, [files]);
    

    Sorry for my bad english. I hope it solves.

    Login or Signup to reply.
  2. You could (as @tkausl suggested) just use the event.target.files value directly instead of referencing the stale files value, but a "more Reacty" way to do this would be to do your FileReader work (and the subsequent operations) in a separate useEffect which runs when files changes:

    // `useEffect` in `Card` component contains code moved from `handleInputChange`
    useEffect(() => {
      console.log(files)
      
      files.forEach((file) => {
        const reader = new FileReader();
    
        reader.onload = (event) => {
          const src = event.target.result;
          const alt = file.name;
        };
      });
    }, [files]);
    

    Depending on what you end up doing in that reader.onload callback, you may want to take care to do cleanup when the files change (e.g. the user goes in and selects extra / different files):

    function addFile(file) {
      // Moved from `useEffect`
      const reader = new FileReader();
    
      let fileRecord = { file, reader, loaded: false };
      reader.onload = (event) => {
        const src = event.target.result;
        const alt = file.name;
    
        fileRecord.loaded = true;
        fileRecord.src = src;
        fileRecord.fileName = file.name;
    
        // whatever else
      };
    
      return fileRecord;
    }
    
    function removeFile(fileRecord) {
      fileRecord.reader.abort();
    
      if (fileRecord.loaded) {
        // cleanup
      }
    }
    
    ...
    
      // Amended `useEffect` in `Card` component:
      useEffect(() => {
        const fileRecords = files.map(addFile);
        return () => fileRecords.map(removeFile); // cleanup
      }, [files]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search