Jacob Hyde
Jacob Hyde

Reputation: 1000

React hooks component callback only has initial state

I am new to react hooks, however I have a problem that I would think is fairly straight forward. Here is my parent component:

import React, { useState, useEffect } from 'react';
import DragAndDrop from '../DragAndDrop';
import Attachment from './Attachment';
import API from '../../services/api';
import '../../styles/components/attachments.scss';

const api = API.create();

const Attachments = ({attachments, type, typeId}) => {

  const [attachmentData, setAttachmentData] = useState([]);

  useEffect(() => {
    setAttachmentData(attachments);
  }, [attachments])

  function onUpload(files) {
    if (typeId) {
      api.AddAttachment(type, typeId, files).then(response => {
        let newAttachments = response.data.data;
        let newAttachmentData = attachmentData;
        newAttachmentData = newAttachmentData.concat(newAttachments);
        setAttachmentData(newAttachmentData);
      });
    }
  }

  return (
    <div className="attachments">
      <h3 className="attachments-title">Attachments</h3>
      <DragAndDrop onUpload={onUpload} />
      {attachmentData.map((attachment, index) => (
        <Attachment key={index} attachment={attachment} />
      ))}
    </div>
  );
}

export default Attachments;

attachments is passed in from the parent component async, which is why I'm using the useEffect function.

This all works fine, and the child Attachment components are rendered when the data is received.

I have a callback onUpload which is called from DragAndDrop component:

import React, { useCallback } from 'react';
import {useDropzone} from 'react-dropzone';
import '../styles/components/dragAndDrop.scss';

const DragAndDrop = ({onUpload}) => {

  const onDrop = useCallback(acceptedFiles => {
    onUpload(acceptedFiles);
  }, [])

  const {getRootProps, getInputProps} = useDropzone({onDrop});

  return (
    <div>
      <div {...getRootProps({className: 'dropzone'})}>
        <input {...getInputProps()} />
        <p>Drag 'n' drop some files here, or click to select files</p>
      </div>
    </div>
  );

};

export default DragAndDrop;

My problem is, when the callback onUpload in the Attachments component is called, attachmentData is the initial value which is an empty array instead of the being populated with the attachments. In my onUpload function, I'm posting the new uploads to my API which then returns them in the same format as the rest of the attachments. I then want to concat these new attachments to the attachmentData. I need attachmentData to have it's filled in value within the callback. Why is the attachmentData the initial state []? How do I get this to work?

Upvotes: 1

Views: 121

Answers (1)

rzwnahmd
rzwnahmd

Reputation: 1082

The problem is that you're accessing attachmentData in onUpload which becomes stale by the time you use it, so to get the latest attachmentData you can pass a callback function to you updater function setAttachmentData like this:

  function onUpload(files) {
    if (typeId) {
      api.AddAttachment(type, typeId, files).then(response => {
        let newAttachments = response.data.data;
        setAttachmentData(prevAttachmentData => ([...prevAttachmentData, ...newAttachments]));
      });
    }
  }

If you want to access the attachmentsData inside onUpload, you can do so by creating a ref and then updating that ref whenever attachmentsData changes, that way you won't have to pass a function to setAttachmentsData also:

const [attachmentsData, setAttachmentsData] = React.useState([]);
const attachmentsDataRef = React.useRef(attachmentsData);

// Update ref whenever state changes
useEffect(() => {
  attachmentsDataRef.current = attachmentsData;
}, [attachmentsData]);

// Now in onUpload
 function onUpload(files) {
    // Here you can access attachmentsDataRef.current and you'll get updated state everytime
    if (typeId) {
      api.AddAttachment(type, typeId, files).then(response => {
        let newAttachments = response.data.data;
        setAttachmentData([...attachmentsDataRef.current, ...newAttachments]);
      });
    }
  }

Upvotes: 2

Related Questions