Reputation: 1000
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
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