amwill04
amwill04

Reputation: 1360

Assign Typescript type in switch statement

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

Answers (1)

Matt McCutchen
Matt McCutchen

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

Related Questions