Reputation: 1360
Try to write a generic function function to return d3 scale
. but getting the following error as it is reading the wrong type after the switch
statement.
import * as D3Scale from 'd3-scale';
enum scaleIdentites {
linear,
time,
}
interface ScaleProps {
scaleIdentity: scaleIdentites;
range: number[];
domain: number[];
}
export const scale = ({ scaleIdentity, domain }: ScaleProps) => {
let scaleFunction: D3Scale.ScaleLinear<number, number> | D3Scale.ScaleTime<number, number>;
switch (scaleIdentity) {
case scaleIdentites.linear:
scaleFunction = D3Scale.scaleLinear();
scaleFunction.domain([1, 2]); // correctly reads the correct type and doesnt error.
break;
case scaleIdentites.time:
scaleFunction = D3Scale.scaleTime();
scaleFunction.domain([1, 2]); // correctly reads the correct type and doesnt error.
break;
default: {
throw new Error(`Unknow scale ${scaleIdentity}`);
}
}
if (domain) {
scaleFunction.domain(domain); // error as saying should have 0 parameters.
}
};
When inside the case
block it correctly allows me to use a parameter in domain
. Outside it errors.
Upvotes: 2
Views: 10086
Reputation: 30929
The problem is that the 1-argument overloads of ScaleLinear.domain
and ScaleTime.domain
have different parameter types (even though number[]
is assignable to both), and when you have a union type, TypeScript keeps only the call signatures with identical parameter types, which in this case is only the 0-argument overload.
In this example, it doesn't seem too bad to me to duplicate the if (domain) { scaleFunction.domain(domain); }
logic in both cases. If you really want to avoid duplicating that if
statement, you can do:
export const scale = ({ scaleIdentity, domain }: ScaleProps) => {
let scaleFunction: D3Scale.ScaleLinear<number, number> | D3Scale.ScaleTime<number, number>;
let setDomain: (domain: number[]) => void;
switch (scaleIdentity) {
case scaleIdentites.linear:
const linearFunction = scaleFunction = D3Scale.scaleLinear();
setDomain = (domain) => linearFunction.domain(domain);
break;
case scaleIdentites.time:
const timeFunction = scaleFunction = D3Scale.scaleTime();
setDomain = (domain) => timeFunction.domain(domain);
break;
default: {
throw new Error(`Unknow scale ${scaleIdentity}`);
}
}
if (domain) {
setDomain(domain);
}
};
Note the use of a new const
variable because narrowing of let
variables doesn't propagate into callbacks.
Typescript no compatible call signatures error with union types is very similar, but not similar enough for me to vote this as a duplicate.
Upvotes: 2