Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to infer the keys of a Record in TypeScript?

Tags:

typescript

I am using JSS and want to define a style object with both strongly typed keys and values, without defining the keys twice.

First attempt:

const style: Record<string, CSSProperties> = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  ...
}

Now style is not strongly typed, so the compiler gives no warning when accessing style.nonExistingKey.

Second attempt:

If I specify the keys explicitly like this:

const style: Record<'root' | 'title' | ... , CSSProperties> = {
  root: {
    background: 'red'
  },
  ...
}

then I get a strongly typed Record, i.e. style.nonExistingKey will throw an error. However, this method requires duplicating the record keys as they must be explicitly added as a generic argument.

Third attempt:

I can create a strongly typed Record after the fact with the following code:

const styleObj = {
  root: {
    background: 'red'
  },
  title: {
    fontWeight: 'bold'
  },
  ...
}

const style = styleObj as Record<keyof typeof styleObj, CSSProperties>

However, then I lose type-checking for the CSSProperties values of the record, so this is not a good solution.

Is there a way to do something like this:

const style: Record<T, CssProperties> = {
  root: {
    background: 'red'
  },
  ...
}

and have T be automatically inferred as 'root' | 'title' | ... etc.?

like image 881
Geir Sagberg Avatar asked Mar 28 '18 15:03

Geir Sagberg


People also ask

What does record do in TypeScript?

TypeScript Records are a great way to ensure consistency when trying to implement more complex types of data. They enforce key values, and allow you to create custom interfaces for the values. The TypeScript Record type was implemented in TypeScript 2.1, and takes the form Record<K, T> .

What is Keyof TypeScript?

keyof is a keyword in TypeScript which is used to extract the key type from an object type.


2 Answers

You can use a helper function when defining the object. The function will have a type parameters that mandates all properties must be of type CSSProperties using an index signature. Since the function is generic the result will be typed correctly (it will not actually have an index signature but rather just the defined properties)

function createStyleMap<T extends { [name: string]: CSSProperties }>(cfg: T)  {
  return cfg;
}
const style = createStyleMap({
  root: {
  background: 'red'
  },
  title: {
    fontWeight: 'bold'
  }
});

style.root //ok
style['hu'] // error

You can also type it to return Record but I don't think this would add any value

function createStyleMap<T extends { [name: string]: CSSProperties }>(cfg: T) : Record<keyof T, CSSProperties> {
  return cfg;
}
like image 175
Titian Cernicova-Dragomir Avatar answered Oct 21 '22 05:10

Titian Cernicova-Dragomir


Update

After learning TypeScript more, I managed to get this working in one function. So here is new, short and working solution.

Helper functions:

import { StyleRulesCallback, Theme } from 'material-ui/styles';
import { CSSProperties } from 'react';

export const createStyleMap =
    <T extends keyof any>(callback: (theme: Theme) => Record<T, CSSProperties>):
    StyleRulesCallback<T extends string ? T : never> => callback;

Example of component:

import { withStyles, WithStyles } from 'material-ui/styles';
import React from 'react';
import { createStyleMap } from '../utils/utils';

interface OwnProps {
    dummyName: string;
}

const styles = createStyleMap(theme =>
    ({
        content: {
            height: '100%',
            padding: theme.spacing.unit * 3,
            overflowY: 'auto',
            boxSizing: 'border-box'
        },
        tableHead: {
            height: 56
        }
    })
);

type Props = OwnProps 
    & WithStyles<keyof ReturnType<typeof styles>>;

class DummyComponent extends React.Component<Props> {
    render() {
        return <div className={this.props.classes.content}/>;
    }
}

export default withStyles(styles)(DummyComponent);
like image 28
user3781568 Avatar answered Oct 21 '22 03:10

user3781568