In my project, using an image crop section for that purpose I refer to the following code https://codesandbox.io/s/react-image-crop-demo-with-react-hooks-forked-67vcek?file=/src/useDebounceEffect.ts:0-316.
When I use the files with the same file structure in the above path it shows the following errors too.
error
ERROR in src/imagePreview.ts:8:19
TS2345: Argument of type '(value: Blob | PromiseLike<Blob>) => void' is not assignable to parameter of type 'BlobCallback'.
Types of parameters 'value' and 'blob' are incompatible.
Type 'Blob | null' is not assignable to type 'Blob | PromiseLike<Blob>'.
Type 'null' is not assignable to type 'Blob | PromiseLike<Blob>'.
6 | function toBlob(canvas: HTMLCanvasElement): Promise<Blob> {
7 | return new Promise((resolve) => {
> 8 | canvas.toBlob(resolve)
| ^^^^^^^
9 | })
10 | }
11 |
ERROR in src/useDebounceEffect.ts:10:27
TS2345: Argument of type 'DependencyList | undefined' is not assignable to parameter of type '[]'.
Type 'undefined' is not assignable to type '[]'.
8 | useEffect(() => {
9 | const t = setTimeout(() => {
> 10 | fn.apply(undefined, deps)
| ^^^^
11 | }, waitTime)
12 |
13 | return () => {
App.tsx
import React, { useState, useRef } from 'react'
import ReactCrop, {
centerCrop,
makeAspectCrop,
Crop,
PixelCrop,
} from 'react-image-crop'
import { canvasPreview } from './canvasPreview'
import { useDebounceEffect } from './useDebounceEffect'
import 'react-image-crop/dist/ReactCrop.css'
// This is to demonstate how to make and center a % aspect crop
// which is a bit trickier so we use some helper functions.
function centerAspectCrop(
mediaWidth: number,
mediaHeight: number,
aspect: number,
) {
return centerCrop(
makeAspectCrop(
{
unit: '%',
width: 90,
},
aspect,
mediaWidth,
mediaHeight,
),
mediaWidth,
mediaHeight,
)
}
export default function App() {
const [imgSrc, setImgSrc] = useState('')
const previewCanvasRef = useRef<HTMLCanvasElement>(null)
const imgRef = useRef<HTMLImageElement>(null)
const [crop, setCrop] = useState<Crop>()
const [completedCrop, setCompletedCrop] = useState<PixelCrop>()
const [scale, setScale] = useState(1)
const [rotate, setRotate] = useState(0)
const [aspect, setAspect] = useState<number | undefined>(16 / 9)
function onSelectFile(e: React.ChangeEvent<HTMLInputElement>) {
if (e.target.files && e.target.files.length > 0) {
setCrop(undefined) // Makes crop preview update between images.
const reader = new FileReader()
reader.addEventListener('load', () =>
setImgSrc(reader.result.toString() || ''),
)
reader.readAsDataURL(e.target.files[0])
}
}
function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) {
if (aspect) {
const { width, height } = e.currentTarget
setCrop(centerAspectCrop(width, height, aspect))
}
}
useDebounceEffect(
async () => {
if (
completedCrop?.width &&
completedCrop?.height &&
imgRef.current &&
previewCanvasRef.current
) {
// We use canvasPreview as it's much faster than imgPreview.
canvasPreview(
imgRef.current,
previewCanvasRef.current,
completedCrop,
scale,
rotate,
)
}
},
100,
[completedCrop, scale, rotate],
)
function handleToggleAspectClick() {
if (aspect) {
setAspect(undefined)
} else if (imgRef.current) {
const { width, height } = imgRef.current
setAspect(16 / 9)
setCrop(centerAspectCrop(width, height, 16 / 9))
}
}
return (
<div className="App">
<div className="Crop-Controls">
<input type="file" accept="image/*" onChange={onSelectFile} />
<div>
<label htmlFor="scale-input">Scale: </label>
<input
id="scale-input"
type="number"
step="0.1"
value={scale}
disabled={!imgSrc}
onChange={(e) => setScale(Number(e.target.value))}
/>
</div>
<div>
<label htmlFor="rotate-input">Rotate: </label>
<input
id="rotate-input"
type="number"
value={rotate}
disabled={!imgSrc}
onChange={(e) =>
setRotate(Math.min(180, Math.max(-180, Number(e.target.value))))
}
/>
</div>
<div>
<button onClick={handleToggleAspectClick}>
Toggle aspect {aspect ? 'off' : 'on'}
</button>
</div>
</div>
{Boolean(imgSrc) && (
<ReactCrop
crop={crop}
onChange={(_, percentCrop) => setCrop(percentCrop)}
onComplete={(c) => setCompletedCrop(c)}
aspect={aspect}
>
<img
ref={imgRef}
alt="Crop me"
src={imgSrc}
style={{ transform: `scale(${scale}) rotate(${rotate}deg)` }}
onLoad={onImageLoad}
/>
</ReactCrop>
)}
<div>
{Boolean(completedCrop) && (
<canvas
ref={previewCanvasRef}
style={{
border: '1px solid black',
objectFit: 'contain',
width: completedCrop.width,
height: completedCrop.height,
}}
/>
)}
</div>
</div>
)
}
imagePreview.ts
import { PixelCrop } from 'react-image-crop'
import { canvasPreview } from './canvasPreview'
let previewUrl = ''
function toBlob(canvas: HTMLCanvasElement): Promise<Blob> {
return new Promise((resolve) => {
canvas.toBlob(resolve)
})
}
// Returns an image source you should set to state and pass
// `{previewSrc && <img alt="Crop preview" src={previewSrc} />}`
export async function imgPreview(
image: HTMLImageElement,
crop: PixelCrop,
scale = 1,
rotate = 0,
) {
const canvas = document.createElement('canvas')
canvasPreview(image, canvas, crop, scale, rotate)
const blob = await toBlob(canvas)
if (previewUrl) {
URL.revokeObjectURL(previewUrl)
}
previewUrl = URL.createObjectURL(blob)
return previewUrl
}
useDebounceEffect.ts
import { useEffect, DependencyList } from 'react'
export function useDebounceEffect(
fn: () => void,
waitTime: number,
deps?: DependencyList,
) {
useEffect(() => {
const t = setTimeout(() => {
fn.apply(undefined, deps)
}, waitTime)
return () => {
clearTimeout(t)
}
}, deps)
}
Please give me some suggestions to fix this problem.
