Skip to content

Migrate withStyles to use hooks #1508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 27, 2021
Merged
30 changes: 15 additions & 15 deletions packages/react-jss/.size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
{
"react-jss.js": {
"bundled": 141808,
"minified": 51596,
"gzipped": 17155
"bundled": 137324,
"minified": 49957,
"gzipped": 16685
},
"react-jss.min.js": {
"bundled": 108758,
"minified": 41452,
"gzipped": 14271
"bundled": 104274,
"minified": 39816,
"gzipped": 13795
},
"react-jss.cjs.js": {
"bundled": 26225,
"minified": 11318,
"gzipped": 3641
"bundled": 21954,
"minified": 9457,
"gzipped": 3161
},
"react-jss.esm.js": {
"bundled": 24188,
"minified": 9724,
"gzipped": 3418,
"bundled": 20065,
"minified": 7976,
"gzipped": 2945,
"treeshaked": {
"rollup": {
"code": 475,
"import_statements": 417
"code": 426,
"import_statements": 368
},
"webpack": {
"code": 2026
"code": 1945
}
}
}
Expand Down
184 changes: 32 additions & 152 deletions packages/react-jss/src/withStyles.js
Original file line number Diff line number Diff line change
@@ -1,191 +1,71 @@
// @flow
import * as React from 'react'
import hoistNonReactStatics from 'hoist-non-react-statics'
import {type StyleSheet, type Classes} from 'jss'
import {ThemeContext} from 'theming'
import {type Classes} from 'jss'
import {ThemeContext as DefaultThemeContext} from 'theming'

import type {HOCProps, HOCOptions, Styles, InnerProps, DynamicRules} from './types'
import type {HOCProps, HOCOptions, Styles, InnerProps} from './types'
import getDisplayName from './getDisplayName'
import memoize from './utils/memoizeOne'
import mergeClasses from './utils/mergeClasses'
import JssContext from './JssContext'
import getSheetIndex from './utils/getSheetIndex'
import {
createStyleSheet,
updateDynamicRules,
addDynamicRules,
removeDynamicRules
} from './utils/sheets'
import {manageSheet, unmanageSheet} from './utils/managers'
import getSheetClasses from './utils/getSheetClasses'

interface State {
dynamicRules: ?DynamicRules;
sheet: ?StyleSheet;
classes: {};
}
import createUseStyles from './createUseStyles'

const NoRenderer = (props: {children?: React.Node}) => props.children || null

const noTheme = {}

type CreateWithStyles = <Theme>(
Styles<Theme>,
HOCOptions<Theme> | void
) => <Props: InnerProps>(React.ComponentType<Props>) => React.ComponentType<Props>
type CreateWithStyles = <Theme: {}>(Styles<Theme>, HOCOptions<Theme> | void) => any => Classes

/**
* HOC creator function that wrapps the user component.
*
* `withStyles(styles, [options])(Component)`
*/

const createWithStyles: CreateWithStyles = <Theme>(styles, options = {}) => {
const {index = getSheetIndex(), theming, injectTheme, ...sheetOptions} = options
const isThemingEnabled = typeof styles === 'function'
const ThemeConsumer = (theming && theming.context.Consumer) || ThemeContext.Consumer
const ThemeContext = theming ? theming.context : DefaultThemeContext

return <Props: InnerProps>(InnerComponent = NoRenderer) => {
const displayName = getDisplayName(InnerComponent)

const getTheme = (props): Theme => (isThemingEnabled ? props.theme : ((noTheme: any): Theme))

class WithStyles extends React.Component<HOCProps<Theme, Props>, State> {
static displayName = `WithStyles(${displayName})`
const mergeClassesProp = memoize(
(sheetClasses, classesProp): Classes =>
classesProp ? mergeClasses(sheetClasses, classesProp) : sheetClasses
)

// $FlowFixMe[prop-missing]
static defaultProps = {...InnerComponent.defaultProps}
const hookOptions = Object.assign((sheetOptions: any), {
theming,
index,
name: displayName
})

static createState(props) {
const sheet = createStyleSheet({
styles,
theme: getTheme(props),
index,
name: displayName,
context: props.jssContext,
sheetOptions
})
const useStyles = createUseStyles(styles, hookOptions)

if (!sheet) {
return {classes: {}, dynamicRules: undefined, sheet: undefined}
}
const WithStyles = React.forwardRef((props: HOCProps<Theme, Props>, ref) => {
const theme = React.useContext(ThemeContext)

const dynamicRules = addDynamicRules(sheet, props)
const newProps: Props & {theme: any} = {...props}

return {
sheet,
dynamicRules,
classes: getSheetClasses(sheet, dynamicRules)
}
if (injectTheme && newProps.theme == null) {
newProps.theme = theme
}

static manage(props, state) {
const {sheet} = state
if (sheet) {
manageSheet({
sheet,
index,
context: props.jssContext,
theme: getTheme(props)
})
}
}
const sheetClasses = useStyles(newProps)

static unmanage(props, state) {
const {sheet, dynamicRules} = state

if (sheet) {
unmanageSheet({
context: props.jssContext,
index,
sheet,
theme: getTheme(props)
})

if (dynamicRules) {
removeDynamicRules(sheet, dynamicRules)
}
}
}
const classes = mergeClassesProp(sheetClasses, props.classes)

mergeClassesProp = memoize(
(sheetClasses, classesProp): Classes =>
classesProp ? mergeClasses(sheetClasses, classesProp) : sheetClasses
)
return <InnerComponent {...newProps} classes={classes} ref={ref} />
})

constructor(props: HOCProps<Theme, Props>) {
super(props)
WithStyles.displayName = `WithStyles(${displayName})`

this.state = WithStyles.createState(props)
const {registry} = props.jssContext
const {sheet} = this.state
if (sheet && registry) {
registry.add(sheet)
}
}
// $FlowFixMe[prop-missing] https://github.com/facebook/flow/issues/7467
WithStyles.defaultProps = {...InnerComponent.defaultProps}

componentDidMount() {
const {props, state} = this
if (props && state) {
WithStyles.manage(props, state)
}
}
// $FlowFixMe[prop-missing]
WithStyles.InnerComponent = InnerComponent

componentDidUpdate(prevProps: HOCProps<Theme, Props>, prevState: State) {
if (isThemingEnabled && this.props.theme !== prevProps.theme) {
const newState = WithStyles.createState(this.props)
WithStyles.manage(this.props, newState)
WithStyles.unmanage(prevProps, prevState)

// eslint-disable-next-line react/no-did-update-set-state
this.setState(newState)
} else if (this.state.sheet && this.state.dynamicRules) {
// Only update the rules when we don't generate a new sheet
updateDynamicRules(this.props, this.state.sheet, this.state.dynamicRules)
}
}

componentWillUnmount() {
WithStyles.unmanage(this.props, this.state)
}

render() {
const {innerRef, jssContext, theme, classes, ...rest} = this.props
const {classes: sheetClasses} = this.state
const props = {
...rest,
classes: this.mergeClassesProp(sheetClasses, classes)
}

if (innerRef) props.ref = innerRef
if (injectTheme) props.theme = theme

return <InnerComponent {...props} />
}
}

const JssContextSubscriber = React.forwardRef((props, ref) => (
<JssContext.Consumer>
{context => {
if (isThemingEnabled || injectTheme) {
return (
<ThemeConsumer>
{theme => (
<WithStyles innerRef={ref} theme={theme} {...props} jssContext={context} />
)}
</ThemeConsumer>
)
}

return <WithStyles innerRef={ref} {...props} jssContext={context} theme={noTheme} />
}}
</JssContext.Consumer>
))

JssContextSubscriber.displayName = `JssContextSubscriber(${displayName})`
// $FlowFixMe[prop-missing] - React's types should allow custom static properties on component.
JssContextSubscriber.InnerComponent = InnerComponent

return hoistNonReactStatics(JssContextSubscriber, InnerComponent)
return hoistNonReactStatics(WithStyles, InnerComponent)
}
}

Expand Down