There's a difference between the following two:
<Component style={{ flexDirection: 'row' }} />
// compared to
const style = { flexDirection: 'row' };
<Component style={{ flexDirection: 'row' }} />;
According to the typings behind the style prop, which is a React.CSSProperties, it expects flexDirection to be of type FlexDirection, which is an union type of 'row' | 'column' | ....
In the first case, TypeScript is smart enough to realize that you're trying to construct a React.CSSProperties and therefore it goes "we expect one of those string values, and 'row' is one of them, good".
In the second case, when you pre-define the object, TypeScript performs type widening and basically thinks flexDirection is just meant to be a string. Which is usually the case, which is why it's the default interpretation. But when you then pass this value to style, it now sees your object's flexDirection as a string instead of the 'row' constant.
The most developer-friendly solution I've come up with over the years (both my friends and I stumbled upon this issue at times, although not always regarding React) is using a kind of generic helper function:
// Using this as example, but you can directly use React.CSSProperties
// (or really any other type that benefits from this "pattern" below)
interface CSSProperties {
flexDirection?: 'row' | 'column';
}
// This simply returns the object itself, but we benefit from the special typing.
// As for the cost of a single simple function call? Negligible and worth it.
function checkStylesheets<T extends { [key in keyof T]: CSSProperties }>(obj: T): T {
return obj;
}
// To showcase the original problem: TypeScript sees 'row' as just a string
const widenedStyle = { flexDirection: 'row' };
// typeof widenedStyle = { flexDirection: string }
const styles = checkStylesheets({
menuContainer: {
flexDirection: 'row',
},
unknownProperty: {
// Type '{ nonExistingField: number; }' is not assignable to type 'CSSProperties'.
// Object literal may only specify known properties, and 'nonExistingField' does not exist in type 'CSSProperties'.ts(2322)
nonExistingField: 123,
},
invalidFlexDirection: {
// Type '"invalid"' is not assignable to type '"row" | "column" | undefined'.ts(2322)
flexDirection: 'invalid',
},
notEvenAString: {
// Type 'number' is not assignable to type '"row" | "column" | undefined'.ts(2322)
flexDirection: 5,
}
});
// typeof styles = {
// menuContainer: CSSProperties;
// unknownProperty: CSSProperties;
// invalidFlexDirection: CSSProperties;
// notEvenAString: CSSProperties;
// }
const someStyle: CSSProperties = styles.menuContainer;
// Property 'nonExistingName' does not exist on type '{ menuContainer: CSSProperties; unknownProperty: CSSProperties; invalidFlexDirection: CSSProperties; notEvenAString: CSSProperties; }'.ts(2339)
const anotherStyle: CSSProperties = styles.nonExistingName;
simplified the example with a custom CSSProperties interface
The above solution/example lists some of the resulting styles in the comments, along with all the TypeScript errors it produces.
Q: Why the need for a helper function? Why not simply set or cast the type of styles to Record<string, CSSProperties> or similar?
A: By using this trick, checkStylesheets will validate that the passed object fits the requirement of being an object with only CSSProperties fields. But because of the generic nature with keyof T, it will the object with the same keys, but as values CSSProperties.
The main benefot of this is that styles keeps its keys. Which means TypeScript will warn us for e.g. styles.nonExistingStyle, very handy for having intellisense and spotting typos. Especially since the style prop accepts undefined!
Q: Can't we just do const styles = { ... } as const; instead?
A: Yes, that'll work, but TypeScript won't validate your styles until you actually pass the object to the style prop. At that point, the style prop will complain, while with the above solution, TypeScript would already warn you when defining styles, regardless of whether you use it or not. There's also the fact that styles's type will be a very complex object where intellisense (and TypeScript errors) will take up your whole screen once you have several (non-tiny) styles in your object. Very different from a simple { styleA: CSSProperties; styleB: CSSProperties; ... }.
Q: Can't we just do const styles: { styleA: CSSProperties } = { styleA: {...} };?
A: Yes, that'll work just as well, but it's double work. It's a completely fine solution, now it just depends on which coding style you prefer. This even has the advantage of defining the type of styles separately (e.g. as an interface) if you prefer seeing e.g. const styles = MyStyles instead of const styles = { styleA: CSSProperties; ... } in intellisense.