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.?
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> .
keyof is a keyword in TypeScript which is used to extract the key type from an object type.
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;
}
After learning TypeScript more, I managed to get this working in one function. So here is new, short and working solution.
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;
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With