Skip to content

Commit ea195bf

Browse files
authored
Improve Types for Hooks (#1460)
This commit applies changes to ensure the `createUseStyles` hook has accurate TS intellisense for props and themes. The details of the work may be found below. Note: Changes are for TypeScript files **only**. Minimal changes were applied in order to minimize regression testing and the potential for cascading negative effects. General: * Updated .eslintignore to ignore TSX type tests in addition to TS type tests. JSS: * `Style` now expects type parameters for `Props` and `Theme`. * `JssStyle` now expects type parameters for `Props` and `Theme`. * `Func` now expects type parameters for `Props` and `Theme`. * Types are arranged to prevent unnecessary/confusing parameter shadowing. Once a function in a style object is introduced, if the function returns an object, none of the returned object's properties (or nested properties) may define a function that has access to `Props` or `Theme`. * Updated tests for `Styles` type. * Where necessary for the compiler, updated other types (mainly for `createStyleSheet` and `StyleSheet`. * Includes minor automated changes. React-JSS: * `createUseStyles` now expects type parameters for `Props` and `Theme`. * `data` in `useStyles` now expects the proper typed argument (`Props` with an optional `Theme`.) * Types are arranged to prevent unnecessary/confusing parameter shadowing. If the argument to `createUseStyles` is a function of `Theme`, then no properties (or nested properties) in the object that the function returns are permitted to define functions with access to `Theme`. * Updated types for `withStyles` (and related types) to be compatible with new changes. * Added several typed tests for `createUseStyles` and `withStyles` to ensure that everything behaves as expected.
1 parent b0510be commit ea195bf

File tree

6 files changed

+481
-31
lines changed

6 files changed

+481
-31
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ flow-typed/
33
packages/**/dist/
44
examples/**/static/
55
*.ts
6+
*.tsx

packages/jss/src/index.d.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,36 @@ import {Properties as CSSProperties} from 'csstype'
77
// TODO: refactor to only include Observable types if plugin is installed.
88
import {Observable} from 'indefinite-observable'
99

10-
// TODO: Type data better, currently typed as any for allowing to override it
11-
type Func<R> = ((data: any) => R)
10+
type Func<P, T, R> = T extends undefined ? ((data: P) => R) : ((data: P & {theme: T}) => R)
1211

1312
type NormalCssProperties = CSSProperties<string | number>
1413
type NormalCssValues<K> = K extends keyof NormalCssProperties ? NormalCssProperties[K] : JssValue
1514

16-
export type JssStyle =
15+
export type JssStyle<Props = any, Theme = undefined> =
1716
| {
1817
[K in keyof NormalCssProperties]:
1918
| NormalCssValues<K>
20-
| JssStyle
21-
| Func<NormalCssValues<K> | JssStyle | undefined>
19+
| JssStyle<Props, Theme>
20+
| Func<Props, Theme, NormalCssValues<K> | JssStyle<undefined, undefined> | undefined>
2221
| Observable<NormalCssValues<K> | JssStyle | undefined>
2322
}
2423
| {
2524
[K: string]:
2625
| JssValue
27-
| JssStyle
28-
| Func<JssValue | JssStyle | undefined>
26+
| JssStyle<Props, Theme>
27+
| Func<Props, Theme, JssValue | JssStyle<undefined, undefined> | undefined>
2928
| Observable<JssValue | JssStyle | undefined>
3029
}
3130

32-
export type Styles<Name extends string | number | symbol = string> = Record<
31+
export type Styles<
32+
Name extends string | number | symbol = string,
33+
Props = unknown,
34+
Theme = undefined
35+
> = Record<
3336
Name,
34-
| JssStyle
37+
| JssStyle<Props, Theme>
3538
| string
36-
| Func<JssStyle | string | null | undefined>
39+
| Func<Props, Theme, JssStyle<undefined, undefined> | string | null | undefined>
3740
| Observable<JssStyle | string | null | undefined>
3841
>
3942
export type Classes<Name extends string | number | symbol = string> = Record<Name, string>
@@ -213,7 +216,10 @@ export interface StyleSheet<RuleName extends string | number | symbol = string |
213216
* Create and add rules.
214217
* Will render also after Style Sheet was rendered the first time.
215218
*/
216-
addRules(styles: Partial<Styles<RuleName>>, options?: Partial<RuleOptions>): Rule[]
219+
addRules(
220+
styles: Partial<Styles<RuleName, any, undefined>>,
221+
options?: Partial<RuleOptions>
222+
): Rule[]
217223
/**
218224
* Get a rule by name.
219225
*/
@@ -248,7 +254,7 @@ export interface JssOptions {
248254

249255
export interface Jss {
250256
createStyleSheet<Name extends string | number | symbol>(
251-
styles: Partial<Styles<Name>>,
257+
styles: Partial<Styles<Name, any, undefined>>,
252258
options?: StyleSheetFactoryOptions
253259
): StyleSheet<Name>
254260
removeStyleSheet(sheet: StyleSheet): this

packages/jss/tests/types/Styles.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ interface Props {
55
flag?: boolean
66
}
77

8+
interface Theme {
9+
color: string
10+
}
11+
812
declare const color$: Observable<'cyan'>
913
declare const style$: Observable<{
1014
backgroundColor: 'fuchsia'
1115
transform: 'translate(0px, 205px)'
1216
}>
1317

14-
const styles: Styles = {
18+
// General Types Check
19+
const styles: Styles<string, Props> = {
1520
basic: {
1621
textAlign: 'center',
1722
display: 'flex',
@@ -22,7 +27,7 @@ const styles: Styles = {
2227
textAlign: 'center',
2328
display: 'flex',
2429
width: '100%',
25-
justifyContent: (props: Props) => (props.flag ? 'center' : undefined)
30+
justifyContent: props => (props.flag ? 'center' : undefined)
2631
},
2732
inner: {
2833
textAlign: 'center',
@@ -33,7 +38,7 @@ const styles: Styles = {
3338
fontSize: 12
3439
}
3540
},
36-
func: (props: Props) => ({
41+
func: props => ({
3742
display: 'flex',
3843
flexDirection: 'column',
3944
justifyContent: 'center',
@@ -43,8 +48,8 @@ const styles: Styles = {
4348
position: 'relative',
4449
pointerEvents: props.flag ? 'none' : null
4550
}),
46-
funcNull: (props: Props) => null,
47-
funcWithTerm: (props: Props) => ({
51+
funcNull: props => null,
52+
funcWithTerm: props => ({
4853
width: props.flag ? 377 : 272,
4954
height: props.flag ? 330 : 400,
5055
boxShadow: '0px 2px 20px rgba(0, 0, 0, 0.08)',
@@ -67,3 +72,30 @@ const styles: Styles = {
6772
to: {opacity: 1}
6873
}
6974
}
75+
76+
// Test supplied Props and Theme
77+
// Verify that nested parameter declarations are banned
78+
const stylesPropsAndTheme: Styles<string, Props, Theme> = {
79+
rootParamDeclaration: ({flag, theme}) => ({
80+
fontWeight: 'bold',
81+
// @ts-expect-error
82+
nothingAllowed: ({flag, theme}) => ''
83+
}),
84+
anotherClass: {
85+
color: 'red',
86+
innerParamDeclaration1: ({flag, theme}) => '',
87+
innerParamDeclaration2: ({flag, theme}) => ({
88+
backgroundColor: 'blue',
89+
// @ts-expect-error
90+
nothingAllowed: ({flag, theme}) => ''
91+
})
92+
}
93+
}
94+
95+
// Test the className types
96+
const stylesClassNames: Styles<number, unknown, unknown> = {
97+
// @ts-expect-error
98+
stringClassName: '',
99+
[1]: '',
100+
[2]: ''
101+
}

packages/react-jss/src/index.d.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,21 @@ declare const JssContext: Context<{
3737
disableStylesGeneration: boolean
3838
}>
3939

40-
type ClassesForStyles<S extends Styles | ((theme: any) => Styles)> = Classes<
41-
S extends (theme: any) => Styles ? keyof ReturnType<S> : keyof S
42-
>
40+
type ClassesForStyles<
41+
S extends Styles<any, any, any> | ((theme: any) => Styles<any, any, undefined>)
42+
> = Classes<S extends (theme: any) => Styles<any, any, undefined> ? keyof ReturnType<S> : keyof S>
4343

44-
interface WithStylesProps<S extends Styles | ((theme: any) => Styles)> {
44+
interface WithStylesProps<
45+
S extends Styles<any, any, any> | ((theme: any) => Styles<any, any, undefined>)
46+
> {
4547
classes: ClassesForStyles<S>
4648
}
4749
/**
4850
* @deprecated Please use `WithStylesProps` instead
4951
*/
50-
type WithStyles<S extends Styles | ((theme: any) => Styles)> = WithStylesProps<S>
52+
type WithStyles<
53+
S extends Styles<any, any, any> | ((theme: any) => Styles<any, any, undefined>)
54+
> = WithStylesProps<S>
5155

5256
declare global {
5357
namespace Jss {
@@ -72,26 +76,25 @@ interface CreateUseStylesOptions<Theme = DefaultTheme> extends BaseOptions<Theme
7276
name?: string
7377
}
7478

75-
declare function createUseStyles<Theme = DefaultTheme, C extends string = string>(
76-
styles: Styles<C> | ((theme: Theme) => Styles<C>),
79+
declare function createUseStyles<C extends string = string, Props = unknown, Theme = DefaultTheme>(
80+
styles: Styles<C, Props, Theme> | ((theme: Theme) => Styles<C, Props, undefined>),
7781
options?: CreateUseStylesOptions<Theme>
78-
): (data?: unknown) => Classes<C>
82+
): (data?: Props & {theme?: Theme}) => Classes<C>
7983

8084
type GetProps<C> = C extends ComponentType<infer P> ? P : never
8185

82-
declare function withStyles<
83-
ClassNames extends string | number | symbol,
84-
S extends Styles<ClassNames> | ((theme: any) => Styles<ClassNames>)
85-
>(
86-
styles: S,
86+
declare function withStyles<ClassNames extends string | number | symbol, Props, Theme>(
87+
styles:
88+
| Styles<ClassNames, Props, Theme>
89+
| ((theme: Theme) => Styles<ClassNames, Props, undefined>),
8790
options?: WithStylesOptions
8891
): <C>(
8992
comp: C
9093
) => ComponentType<
9194
JSX.LibraryManagedAttributes<
9295
C,
9396
Omit<GetProps<C>, 'classes'> & {
94-
classes?: Partial<ClassesForStyles<S>>
97+
classes?: Partial<ClassesForStyles<typeof styles>>
9598
innerRef?: RefObject<any> | ((instance: any) => void)
9699
}
97100
>

0 commit comments

Comments
 (0)