I have a useEffect function that takes an image, resizes it for display and makes it ready for upload. While this works I get the React warning to list the function in the useEffect dependency list. But when I do this it causes continuous re-renders even when moving the function to a useCallback. The code is as follows ...
import React, { useEffect, useRef, useCallback } from 'react'
const Canvas = (props) => {
const { width = 180, height = 220, img, onChange = null, setFile = null} = props
const canvas = useRef(null)
const setBlobFile = useCallback(
(blob) => {
setFile(blob)
},
[setFile]
)
useEffect(() => {
if (img) {
console.log("Canvas useEffect ...", img)
var image = new Image()
image.src = img
image.onload = () => {
canvas.current.getContext("2d")
canvas.current.getContext("2d").clearRect(0, 0, width, height);
canvas.current.getContext("2d").drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height)
canvas.current.toBlob((blob) => {
setBlobFile(blob)
})
}
}
}, [img, width, height, setBlobFile])
const onFileSelect = (e) => {
const objectURL = URL.createObjectURL(e.target.files[0])
onChange(objectURL)
}
return (
<div onClick={onClick}>
<label htmlFor='upload'>
<canvas ref={canvas} width={width} height={height} />
<input type='file' id='upload' onChange={(e) => onFileSelect(e)} style={{ display: 'none' }} />
</label>
</div>
)
}
export { Canvas as default }
Parent Component provides the setFile routine to set the file for upload
...
const [selectedFile, setSelectedFile] = useState(new File([""], ""))
const setFile = (aBlob) => {
var img = new Image()
img = aBlob
setSelectedFile(new File([aBlob], "image.png", {
type: 'image/png',
}))
}
...
Any ideas why I'm seeing this behaviour? I thought the useCallback() approach was supposed to stop the re-renders but it seems to create them. How can I avoid the warning but also stop the re-rendering behaviour? Thanks for taking a look.
You have to use useCallback
where the function is created, not where it is used.
const setBlobFile = useCallback(
(blob) => {
setFile(blob)
},
[setFile]
)
in Canvas
is useless for preventing rerenders when setFile
changes because setBlobFile
will still change when setFile
changes, since setFile
is passed as a dependency.
Instead you need to to use useCallback
in the parent component where setFile
is created in the first place:
const [selectedFile, setSelectedFile] = useState(new File([""], ""))
const setFile = useCallback((aBlob) => {
var img = new Image()
img = aBlob
setSelectedFile(new File([aBlob], "image.png", {
type: 'image/png',
}))
}, [setSelectedFile]);
This is will work as expected because useState
guarantees that the setter function (setSelectedFile
) will never change between renders and therefore setFile
will never change between renders.