Child component rerenders when parent does, even though React.memo returns true

Viewed 1112

I have a nested component that initiates a HTTP request for uploading an image. It displays progress.

const PreviewItem = React.memo(function PreviewItem(props: PreviewItemProps) {
    const {fileObject, handleRemove, classes, onUploadFinished} = props

    const [isLoading, setIsLoading] = useState(false)
    const [completed, setCompleted] = useState(0);

    const cancelTokenSource = axios.CancelToken.source();

    useEffect(() => {
        const data = new FormData();
        data.append('file', fileObject.file);

        const config: AxiosRequestConfig = {
            onUploadProgress: function (progressEvent) {
                const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
                setCompleted(percentCompleted)
            },
            cancelToken: cancelTokenSource.token
        };

        if (!fileObject.isUploaded) {
            setIsLoading(true)
            avantiApi.post<DirectUploadResponse>("images/upload", data, config).then(response => {

                setIsLoading(false)

                if (onUploadFinished) {
                    onUploadFinished(response.data, fileObject)
                }
            }).catch(e => console.log("Upload Error", e))
        }
        return function abortUpload() {
            console.log("Use effect...aborting...")
            cancelTokenSource.cancel("Cancelled")
        }
    }, [fileObject])

    return <> ..... </>
}, function (prevProps: Readonly<PropsWithChildren<PreviewItemProps>>, nextProps: Readonly<PropsWithChildren<PreviewItemProps>>) {
    const areEqual = prevProps.fileObject === nextProps.fileObject
    console.log("Are equal?", areEqual)
    return areEqual
})

My issue is that even though React.memo’s propsAreEqual function returns true, the component rerenders - if the parent rerenders - while the image is being uploaded, making the request to restart. Do I miss something on the usage of React.memo?

2 Answers

From the docs:

React.memo only checks for prop changes. If your function component wrapped in React.memo has a useState or useContext Hook in its implementation, it will still rerender when state or context change.

Internally you should check your isLoading state:

useEffect(() => {
  ...
  if(!fileObject.isUploaded && !isLoading) {
    setIsLoading(true)
    ...
  }
  ...
}, [fileObject, isLoading])

I would recommend to change to:

}, function (prevProps: Readonly<PropsWithChildren<PreviewItemProps>>, nextProps: Readonly<PropsWithChildren<PreviewItemProps>>) {
    const areEqual = JSON.parse(prevProps.fileObject) === JSON.parse(nextProps.fileObject)
    console.log("Are equal?", areEqual);
    return areEqual;
})

to make sure that whey are really equals, maybe it's the issue.

Related