Home > Enterprise >  D3.js & Typescript generics - selection.data() & K extends keyof T
D3.js & Typescript generics - selection.data() & K extends keyof T

Time:05-05

Update Here is a link to D3’s selection.data API, and a codesandbox for you hands on types.

The goal is to have a generically typed interface, and pass a property key (also generic) to reference the given key on the given type during implementation.

I can’t seem to get the typing right.

The code

I have an interface with two properties:

interface Props<T, K extends keyof T> {
  data: T[]
  dataKey: K
}

I try to use this in my function:

const Heatmap = <T, K extends keyof T>({ data, dataKey }: Props<T, K>) => {
  …
  const svg = d3.select.<some other chained fns>

  // then later
  svg
   .append(‘g’)
   .selectAll(‘rect’)
   .data<T>(d => d[dataKey]) // problem code
}

Compiler complains under the data callback:

Argument of type '(this: SVGGElement, d: T) => T[K]' is not assignable to parameter of type 'T[] | Iterable<T> | ValueFn<SVGGElement, T, T[] | Iterable<T>>'.
  Type '(this: SVGGElement, d: T) => T[K]' is not assignable to type 'ValueFn<SVGGElement, T, T[] | Iterable<T>>'.
    Type 'T[K]' is not assignable to type 'T[] | Iterable<T>'.
      Type 'T[keyof T]' is not assignable to type 'T[] | Iterable<T>'.
        Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'T[] | Iterable<T>'.
          Type 'T[string]' is not assignable to type 'T[] | Iterable<T>'.
            Type 'T[string]' is not assignable to type 'T[]'.
              Type 'T[keyof T]' is not assignable to type 'T[]'.
                Type 'T[K]' is not assignable to type 'T[]'.
                  Type 'T[keyof T]' is not assignable to type 'T[]'.
                    Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'T[]'.
                      Type 'T[string]' is not assignable to type 'T[]'.ts(2345)

In vanilla js, the data callback looks like:

data(d => d.<key>)

I have looked all over and could not manage to piece it together. I fiddled with mapped types, indexed types, using the Record utility, but I cannot make it work. I also experimented with the optional key parameter callback. Nothing seems to appease tsc. What am I missing here? Why doesn’t this work as I wrote it?

FWIW, the typescript code I have here does work in the browser, but tsc won't compile

TIA

CodePudding user response:

Update The solution below did indeed satisfy the compiler, but only superficially. I discovered that from then on, typescript assumed I was passing a string and complained a lot.

The final fix (as yet) was to use the optional key ValueFn. The declaration files are not terribly helpful, but essentially, I was able to change the .data() invocation to:

.data(data, () => xKey as string)

This only works because I’m using generics and passing the key as props to the react component, but maybe this will help someone along their journey.

Outdated solution below

Found the answer here. All it needed was to return a string, à la:

data(d => String(d[dataKey]))
  • Related