|
| 1 | +# Migrating the `withStyles` HOC |
| 2 | + |
| 3 | +Although we recommend using the new hooks, it's possible that you have class components that cannot be migrated easily at this time. In that case, you can create your own higher order component (HOC) from the provided hooks. This way, you'll have a HOC that stays up-to-date with the latest features, and you'll still have the option of fully migrating to hooks at your own convenience. |
| 4 | + |
| 5 | +A simple solution may look something like this: |
| 6 | + |
| 7 | +```jsx |
| 8 | +import React from 'react' |
| 9 | +import {createUseStyles, useTheme} from 'react-jss' |
| 10 | + |
| 11 | +/** |
| 12 | + * Creates a Higher Order Component that injects the CSS specified in `styles`. |
| 13 | + * @param styles |
| 14 | + */ |
| 15 | +function withStyles(styles) { |
| 16 | + return function(WrappedComponent) { |
| 17 | + const useStyles = createUseStyles(styles) |
| 18 | + |
| 19 | + const StyledComponent = props => { |
| 20 | + const {classes, ...passThroughProps} = props |
| 21 | + const theme = useTheme() |
| 22 | + const reactJssClasses = useStyles({...passThroughProps, theme}) |
| 23 | + |
| 24 | + return <WrappedComponent {...passThroughProps} classes={reactJssClasses} /> |
| 25 | + } |
| 26 | + |
| 27 | + StyledComponent.displayName = `withStyles(${WrappedComponent.name})` |
| 28 | + |
| 29 | + return StyledComponent |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +export default withStyles |
| 34 | +``` |
| 35 | + |
| 36 | +Note that `useTheme` can be excluded if your application is not using a theme. |
| 37 | + |
| 38 | +To learn more about HOCs, see [react's documentation](https://reactjs.org/docs/higher-order-components.html). Since our HOC uses the `createUseStyles` hook under the hood, you can use the regular [hooks documentation](react-jss.md) for help with defining your `styles` objects. |
| 39 | + |
| 40 | +**Warning**: Because this HOC makes use of hooks, it cannot be used as a decorator. |
| 41 | + |
| 42 | +## Adding TypeScript to Your HOC |
| 43 | + |
| 44 | +If you're using TypeScript, you'll likely want to add types for your custom `withStyles` HOC, like so: |
| 45 | + |
| 46 | +```tsx |
| 47 | +import React from 'react' |
| 48 | +import {createUseStyles, useTheme, Styles} from 'react-jss' |
| 49 | + |
| 50 | +type ReactJSSProps = {classes?: ReturnType<ReturnType<typeof createUseStyles>>} |
| 51 | + |
| 52 | +/** |
| 53 | + * Creates a Higher Order Component that injects the CSS specified in `styles`. |
| 54 | + * @param styles |
| 55 | + */ |
| 56 | +function withStyles<C extends string, Pr extends ReactJSSProps, T>( |
| 57 | + styles: Styles<C, Pr, T> | ((theme: T) => Styles<C, Pr>) |
| 58 | +) { |
| 59 | + return function<P extends Pr, S>(WrappedComponent: React.ComponentClass<P, S>): React.FC<P> { |
| 60 | + const useStyles = createUseStyles<C, P, T>(styles) |
| 61 | + |
| 62 | + const StyledComponent: React.FC<P> = (props: P) => { |
| 63 | + const {classes, ...passThroughProps} = props |
| 64 | + const theme = useTheme<T>() |
| 65 | + const reactJssClasses = useStyles({...(passThroughProps as P), theme}) |
| 66 | + |
| 67 | + return <WrappedComponent {...passThroughProps as P} classes={reactJssClasses} /> |
| 68 | + } |
| 69 | + |
| 70 | + StyledComponent.displayName = `withStyles(${WrappedComponent.name})` |
| 71 | + |
| 72 | + return StyledComponent |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +export default withStyles |
| 77 | +``` |
| 78 | + |
| 79 | +This typed HOC enforces consistency with your `RuleNames` and `Theme`. It also enforces consistency between the `Props` you give to `Styles` and the ones you give to your component. |
| 80 | + |
| 81 | +You'll notice that here, we've typed the HOC to accept only class components as arguments. This is because you should be using the provided hooks for your functional components; not only do hooks provide a simpler interface, but they also help clarify which props actually belong to your component. |
| 82 | + |
| 83 | +## Migrating from Decorators |
| 84 | + |
| 85 | +Because this custom HOC makes use of hooks (which are [unusable in class components](https://reactjs.org/docs/hooks-faq.html#:~:text=You%20can't%20use%20Hooks,implementation%20detail%20of%20that%20component.)), you won't be able to use this HOC as a decorator. If you are using decorators in your project, you'll likely have to migrate your code from this: |
| 86 | + |
| 87 | +```javascript |
| 88 | +import React from 'react' |
| 89 | +import decorator1 from 'some-hoc-library' |
| 90 | +import decorator2 from 'another-hoc-library' |
| 91 | +// ... |
| 92 | +import withStyles from 'path/to/custom-hoc' |
| 93 | + |
| 94 | +const styles = { |
| 95 | + /* ... */ |
| 96 | +} |
| 97 | + |
| 98 | +@decorator1 |
| 99 | +@decorator2 |
| 100 | +// ... |
| 101 | +@withStyles(styles) |
| 102 | +class MyComponent extends React.Component { |
| 103 | + // ... |
| 104 | +} |
| 105 | + |
| 106 | +export default MyComponent |
| 107 | +``` |
| 108 | + |
| 109 | +to this: |
| 110 | + |
| 111 | +```javascript |
| 112 | +import React from 'react' |
| 113 | +import decorator1 from 'some-hoc-library' |
| 114 | +import decorator2 from 'another-hoc-library' |
| 115 | +// ... |
| 116 | +import withStyles from 'path/to/custom-hoc' |
| 117 | + |
| 118 | +const styles = { |
| 119 | + /* ... */ |
| 120 | +} |
| 121 | + |
| 122 | +@decorator1 |
| 123 | +@decorator2 |
| 124 | +// ... |
| 125 | +class MyComponent extends React.Component { |
| 126 | + // ... |
| 127 | +} |
| 128 | + |
| 129 | +export default withStyles(styles)(MyComponent) |
| 130 | +``` |
| 131 | + |
| 132 | +If you find yourself using many decorators for your class components, consider migrating away from chained decorators to [composed function calls](https://reactjs.org/docs/higher-order-components.html#convention-maximizing-composability). This is a safer play in the long run since decorators still have not stabilized in the JS standard. |
| 133 | + |
| 134 | +If you don't use decorators or aren't familiar with them, then this won't be a concern for you. |
0 commit comments