Reputation: 21201
I am working on upgrading some old TypeScript code to use the latest compiler version, and I'm having trouble with a call to setTimeout
. The code expects to call the browser's setTimeout
function which returns a number:
setTimeout(handler: (...args: any[]) => void, timeout: number): number;
However, the compiler is resolving this to the node implementation instead, which returns a NodeJS.Timer:
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;
This code does not run in node, but the node typings are getting pulled in as a dependency to something else (not sure what).
How can I instruct the compiler to pick the version of setTimeout
that I want?
Here is the code in question:
let n: number;
n = setTimeout(function () { /* snip */ }, 500);
This produces the compiler error:
TS2322: Type 'Timer' is not assignable to type 'number'.
Upvotes: 439
Views: 382016
Reputation: 150
If your code is not running in node and the node types are coming from a dependency.
install @types/web
if you haven't already
create a file in your project e.g. web-types.d.ts
at the top of the file add the line
/// <reference types="web" />
in your tsconfig under compilerOptions
add
"typeRoots": ["./web-types", "./node_modules/@types"]
This should prioritize browser types over node types.
Upvotes: 4
Reputation: 1
TS2322: Type 'Timer' is not assignable to type 'number'.
The simple solution
abc : any;
for assgin
abc = setInterval or abc = setTimeout
Upvotes: -1
Reputation: 5662
Wanted to mention as well that the spec for NodeJS.Timeout
includes [Symbol.toPrimitive](): number
:
interface Timeout extends Timer {
/**
* If true, the `Timeout` object will keep the Node.js event loop active.
* @since v11.0.0
*/
hasRef(): boolean;
/**
* Sets the timer's start time to the current time, and reschedules the timer to
* call its callback at the previously specified duration adjusted to the current
* time. This is useful for refreshing a timer without allocating a new
* JavaScript object.
*
* Using this on a timer that has already called its callback will reactivate the
* timer.
* @since v10.2.0
* @return a reference to `timeout`
*/
refresh(): this;
[Symbol.toPrimitive](): number;
}
And for compatibility, the other timeout APIs in Node work just fine with the plain integer ids, they don't need to accept the object. The objects are used "server"-side to allow some finer control over keeping the process alive and garbage collection stuff. For example:
function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void;
This means you can use a primitive cast on the result of setTimeout
and setInterval
:
let timeoutId: number | undefined;
timeoutId = Number(setTimeout(callback, ms));
function clear() {
clearTimeout(timeoutId);
}
Doesn't conflict with either API, while not running you into type trouble later on if you needed to depend on it being a primitive value for some other API contract.
Upvotes: 12
Reputation: 11
I was testing my Counter app using RTL and specifically was testing an element to be removed if count reaches 15. Since the component gets destroyed after running the test, setTimeout would still run after that and throw the error saying that React can't perform a state update on unmounted component. So, based on dhilt's answer, I was able to fix my useEffect cleanup function this way:
const [count, setCount] = useState(initialCount);
const [bigSize, setBigSize] = useState(initialCount >= 15);
useEffect(() => {
let id: NodeJS.Timeout;
if(count >= 15) {
id = setTimeout(() => setBigSize(true), 300);
}
return function cleanup() {
clearTimeout(id);
}
});
And here's the test suite:
describe('when the incrementor changes to 5 and "add" button is clicked', () => {
beforeEach(async () => {
userEvent.type(screen.getByLabelText(/Incrementor/), '{selectall}5');
userEvent.click(screen.getByRole('button', {name: "Add to Counter"}));
await screen.findByText('Current Count: 15');
})
it('renders Current Count: 15', () => {
expect(screen.getByText('Current Count: 15')).toBeInTheDocument();
});
it('renders too big and will dissapear after 300ms',async() => {
await waitForElementToBeRemoved(() => screen.queryByText(/size: small/i))
});
})
Upvotes: 0
Reputation: 11595
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });
clearTimeout(timer);
By using ReturnType<fn>
you are getting independence from platform. You won't be forced to use neither any
nor window.setTimeout
which will break if you run the code on nodeJS server (eg. server-side rendered page).
Good news, this is also compatible with Deno!
Upvotes: 656
Reputation: 5249
This works perfectly well for me.
type Timer = ReturnType<typeof setTimeout>
const timer: Timer = setTimeout(() => {}, 1000)
Upvotes: 25
Reputation: 1
I solved this problem by setting
{
"compilerOptions": {
"skipLibCheck": true,
}
}
And create .d.ts
declare namespace NodeJS {
type Timeout = number;
type Timer = number;
}
typescript version 4.2.3
Upvotes: -3
Reputation: 20844
2021 update
Akxe's answer suggests ReturnType<Type>
technique introduced in Typescript 2.3:
let n: ReturnType<typeof setTimeout>;
n = setTimeout(cb, 500);
It is nice and seems to be preferred over explicit casting. But the result type of "n" in this case is "NodeJS.Timeout", and it is possible to use it as follows:
let n: NodeJS.Timeout;
n = setTimeout(cb, 500);
The only problem with ReturnType/NodeJS.Timeout approach is that numeric operations in browser-specific environment still require casting:
if ((n as unknown as number) % 2 === 0) {
clearTimeout(n);
}
Original answer
A workaround that does not affect variable declaration:
let n: number;
n = setTimeout(function () { /* snip */ }, 500) as unknown as number;
Also, in browser-specific environment it is possible to use window
object with no casting:
let n: number;
n = window.setTimeout(function () { /* snip */ }, 500);
Upvotes: 307
Reputation: 7296
If you're targeting setInterval
of window
. Then you can also write it as
let timerId: number = setInterval((()=>{
this.populateGrid(true)
}) as TimerHandler, 5*1000)
}
Upvotes: 1
Reputation: 780
This will likely work with older versions, but with TypeScript version ^3.5.3
and Node.js version ^10.15.3
, you should be able to import the Node-specific functions from the Timers module, i.e.:
import { setTimeout } from 'timers';
That will return an instance of Timeout of type NodeJS.Timeout
that you can pass to clearTimeout
:
import { clearTimeout, setTimeout } from 'timers';
const timeout: NodeJS.Timeout = setTimeout(function () { /* snip */ }, 500);
clearTimeout(timeout);
Upvotes: 14
Reputation: 767
I guess it depends on where you will be running your code.
If your runtime target is server side Node JS, use:
let timeout: NodeJS.Timeout;
global.clearTimeout(timeout);
If your runtime target is a browser, use:
let timeout: number;
window.clearTimeout(timeout);
Upvotes: 47
Reputation: 2007
I faced the same problem and the workaround our team decided to use, was just to use "any" for the timer type. E.g.:
let n: any;
n = setTimeout(function () { /* snip */ }, 500);
It will work with both implementations of setTimeout/setInterval/clearTimeout/clearInterval methods.
Upvotes: -2