Reputation: 19
I have the following code;
App.tsx
export default function App() {
const [canvasRef, canvasWidth, canvasHeight] = useCanvas();
return (
<div>
<canvas
ref={canvasRef}
/>
</div>
)
}
useCanvas.ts
import { useRef, useEffect } from "react";
const canvasWidth = 1200;
const canvasHeight = 600;
export default function useCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
return [canvasRef, canvasWidth, canvasHeight]
}
And yes, this is precisely what I have in my code, however, I'm getting the following error in App.tsx from Vscode;
(JSX attribute) React.ClassAttributes<HTMLCanvasElement>.ref?: string | React.RefObject<HTMLCanvasElement> | ((instance: HTMLCanvasElement | null) => void) | null | undefined
Type 'number | RefObject<HTMLCanvasElement>' is not assignable to type 'string | RefObject<HTMLCanvasElement> | ((instance: HTMLCanvasElement | null) => void) | null | undefined'.
Type 'number' is not assignable to type 'string | RefObject<HTMLCanvasElement> | ((instance: HTMLCanvasElement | null) => void) | null | undefined'.ts(2322)
index.d.ts(143, 9): The expected type comes from property 'ref' which is declared here on type 'DetailedHTMLProps<CanvasHTMLAttributes<HTMLCanvasElement>, HTMLCanvasElement>'
Any advice would be appreciated.
Upvotes: 0
Views: 1545
Reputation: 1222
The return type of your custom hook is (number | React.MutableRefObject<HTMLCanvasElement>)[]
because typescript infers it returns an array containing these types. This is why your deconstructed variables in App.tsx
are not properly typed (if you hover them, youll see they are all of type number | React.MutableRefObject<HTMLCanvasElement>
).
What you need is to use a tuple to tell typescript exactly what is contained in this array.
You could add typing information to your custom hook like so:
import { useRef, useEffect, MutableRefObject } from "react";
const canvasWidth = 1200;
const canvasHeight = 600;
export default function useCanvas(): [MutableRefObject<HTMLCanvasElement>, number, number] {
const canvasRef = useRef<HTMLCanvasElement>(null);
return [canvasRef, canvasWidth, canvasHeight]
}
Upvotes: 0
Reputation: 84902
function useCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
return [canvasRef, canvasWidth, canvasHeight]
}
Since you havn't given a type to the return value, typescript will infer it. When infering the type on an array, typescript is going to assume it's a normal array, not a tuple.
To simplify it, suppose you were returning this:
function () {
return [1, 'a'];
}
Typescript would infer the type to be (number | string)[]
. In other words, an array of any length, where every element can be either a number of a string. It will not infer the type to be [number, string]
. So if you were to try to interact with index 0 of this array, typescript wouldn't be able to tell you that it's definitely a number, only that it's a number or a string.
So your first option is to give an explicit type to the thing you're returning. In my simplified case, it would be:
function () {
const result: [number, string] = [1, 'a'];
return result;
}
// or
function (): [number, string] {
return [1, 'a'];
}
// or
function () {
return [1, 'a'] as [number, string];
}
I'm not exactly sure that the correct types are for your code, but you could inspect the variables and figure it out.
Another option, and probably the simpler one, is to use as const
to help typescript out with inferring the types. as const
tells it that this will never change, and so it will treat it as a tuple instead of an array.
function useCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
return [canvasRef, canvasWidth, canvasHeight] as const;
}
Upvotes: 2