Skip to content

Commit

Permalink
(split)[react-jss] Add flow typings (cssinjs#818)
Browse files Browse the repository at this point in the history
* First commit

* Fixes on createHOC and injectSheet

* More fixes on createHOC and injectSheet

* Better flow typings on createHOC

* Moved types/index.js just to types.js and added InnerProps and OuterProps utility types

* Simplified typings of injectSheet.js

* Updated and fixed some typings in createHOC.js

- Used OuterProps and InnerProps types
- Fixed the props of the returning component

* Removed unnecessary types in ns.js

* Added flow flag to some untyped files

* Exporting StyleSheetFactoryOptions now instead of StyleSheetOptions

* Fix typing of getDisplayName

* Added Context Type and updated Options to use StyleSheetFactoryOptions type

* Moved types/index.js just to types.js and added

* Improved Context Type

* Updated createHoc to use Context type

* Improved typing of the Theming option

* Formatted code with prettier

* Updated size snapshot and lockfile

* Make options in injectSheet optional an default it to an empty object

* Fixed types of the JssProvider props
- Made most props optional
- Only allow one child

* Remove semi eslint rule

* Removed unnecessary types

* Remove unnecessary check

* Updated a if statement and fixed removing dynamic sheet from global registry after component unmounts

* Fixed naming of some type names to always start with an uppercase char

* Renamed generic types of createHOC
  • Loading branch information
gomesalexandre authored and Henri committed Aug 15, 2018
1 parent 2a7b697 commit a77f94d
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 50 deletions.
28 changes: 14 additions & 14 deletions .size-snapshot.json
@@ -1,30 +1,30 @@
{
"dist/react-jss.js": {
"bundled": 237212,
"minified": 65434,
"gzipped": 17994
"bundled": 237652,
"minified": 65587,
"gzipped": 18039
},
"dist/react-jss.min.js": {
"bundled": 201910,
"minified": 56081,
"gzipped": 15810
"bundled": 202343,
"minified": 56230,
"gzipped": 15853
},
"dist/react-jss.cjs.js": {
"bundled": 17646,
"minified": 7319,
"gzipped": 2579
"bundled": 18359,
"minified": 7518,
"gzipped": 2649
},
"dist/react-jss.esm.js": {
"bundled": 17160,
"minified": 6889,
"gzipped": 2485,
"bundled": 17873,
"minified": 7088,
"gzipped": 2552,
"treeshaked": {
"rollup": {
"code": 1908,
"code": 2057,
"import_statements": 214
},
"webpack": {
"code": 3079
"code": 3228
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 22 additions & 4 deletions src/JssProvider.js
@@ -1,11 +1,23 @@
import {Component, Children} from 'react'
// @flow
import {Component, Children, type Element, type ElementType} from 'react'
import PropTypes from 'prop-types'
import type {Jss, GenerateClassName, SheetsRegistry} from 'jss'
import {createGenerateClassNameDefault} from './jss'
import * as ns from './ns'
import contextTypes from './contextTypes'
import propTypes from './propTypes'
import type {Context} from './types'

export default class JssProvider extends Component {
type Props = {
jss?: Jss,
registry?: SheetsRegistry,
generateClassName?: GenerateClassName,
classNamePrefix?: string,
disableStylesGeneration?: boolean,
children: Element<ElementType>
}

export default class JssProvider extends Component<Props> {
static propTypes = {
...propTypes,
generateClassName: PropTypes.func,
Expand All @@ -22,7 +34,7 @@ export default class JssProvider extends Component {
// 1. Check if there is a value passed over props.
// 2. If value was passed, we set it on the child context.
// 3. If value was not passed, we proxy parent context (default context behaviour).
getChildContext() {
getChildContext(): Context {
const {
registry,
classNamePrefix,
Expand All @@ -31,7 +43,7 @@ export default class JssProvider extends Component {
disableStylesGeneration
} = this.props
const sheetOptions = this.context[ns.sheetOptions] || {}
const context = {[ns.sheetOptions]: sheetOptions}
const context: Context = {[ns.sheetOptions]: sheetOptions}

if (registry) {
context[ns.sheetsRegistry] = registry
Expand Down Expand Up @@ -73,6 +85,12 @@ export default class JssProvider extends Component {
return context
}

registry: SheetsRegistry

managers: {}

generateClassName: GenerateClassName

render() {
return Children.only(this.props.children)
}
Expand Down
4 changes: 3 additions & 1 deletion src/compose.js
@@ -1,3 +1,5 @@
// @flow
import type {Classes} from 'jss'
/**
* Adds `composes` property to each top level rule
* in order to have a composed class name for dynamic style sheets.
Expand All @@ -21,7 +23,7 @@
* @param {Object} styles dynamic styles object without static properties
* @return {Object|null}
*/
export default (staticClasses, dynamicClasses) => {
export default (staticClasses: Classes, dynamicClasses: Classes) => {
const combinedClasses = {...staticClasses}

for (const name in dynamicClasses) {
Expand Down
1 change: 1 addition & 0 deletions src/contextTypes.js
@@ -1,3 +1,4 @@
// @flow
import PropTypes from 'prop-types'
import * as ns from './ns'
import propTypes from './propTypes'
Expand Down
71 changes: 52 additions & 19 deletions src/createHoc.js
@@ -1,11 +1,22 @@
import React, {Component} from 'react'
// @flow
import React, {Component, type ComponentType} from 'react'
import PropTypes from 'prop-types'
import defaultTheming from 'theming'
import type {StyleSheet} from 'jss'
import jss, {getDynamicStyles, SheetsManager} from './jss'
import compose from './compose'
import getDisplayName from './getDisplayName'
import * as ns from './ns'
import contextTypes from './contextTypes'
import type {
Options,
Theme,
StylesOrThemer,
InnerProps,
OuterProps,
Context,
SubscriptionId
} from './types'

const env = process.env.NODE_ENV

Expand All @@ -32,7 +43,13 @@ const dynamicStylesNs = Math.random()
*
*/

const getStyles = (stylesOrCreator, theme) => {
type State = {
theme: Theme,
dynamicSheet?: StyleSheet,
classes: {}
}

const getStyles = (stylesOrCreator: StylesOrThemer, theme: Theme) => {
if (typeof stylesOrCreator !== 'function') {
return stylesOrCreator
}
Expand All @@ -57,41 +74,53 @@ let managersCounter = 0
/**
* Wrap a Component into a JSS Container Component.
*
* InnerPropsType: Props of the InnerComponent.
* OuterPropsType: The Outer props the HOC accepts.
*
* @param {Object|Function} stylesOrCreator
* @param {Component} InnerComponent
* @param {Object} [options]
* @return {Component}
*/
export default (stylesOrCreator, InnerComponent, options = {}) => {
export default function createHOC<
InnerPropsType: InnerProps,
InnerComponentType: ComponentType<InnerPropsType>,
OuterPropsType: OuterProps<InnerPropsType, InnerComponentType>
>(
stylesOrCreator: StylesOrThemer,
InnerComponent: InnerComponentType,
options: Options
): ComponentType<OuterPropsType> {
const isThemingEnabled = typeof stylesOrCreator === 'function'
const {theming = defaultTheming, inject, jss: optionsJss, ...sheetOptions} = options
const injectMap = inject ? toMap(inject) : defaultInjectProps
const {themeListener} = theming
const displayName = getDisplayName(InnerComponent)
const defaultClassNamePrefix = env === 'production' ? undefined : `${displayName}-`
const defaultClassNamePrefix = env === 'production' ? '' : `${displayName}-`
const noTheme = {}
const managerId = managersCounter++
const manager = new SheetsManager()
const defaultProps = {...InnerComponent.defaultProps}
// $FlowFixMe defaultProps are not defined in React$Component
const defaultProps: InnerPropsType = {...InnerComponent.defaultProps}
delete defaultProps.classes

class Jss extends Component {
class Jss extends Component<OuterPropsType, State> {
static displayName = `Jss(${displayName})`
static InnerComponent = InnerComponent
static contextTypes = {
...contextTypes,
...(isThemingEnabled && themeListener.contextTypes)
...(isThemingEnabled ? themeListener.contextTypes : {})
}
static propTypes = {
innerRef: PropTypes.func
}
static defaultProps = defaultProps

constructor(props, context) {
constructor(props: OuterPropsType, context: Context) {
super(props, context)
const theme = isThemingEnabled ? themeListener.initial(context) : noTheme

this.state = this.createState({theme}, props)
this.state = this.createState({theme, classes: {}}, props)
}

componentWillMount() {
Expand All @@ -104,13 +133,13 @@ export default (stylesOrCreator, InnerComponent, options = {}) => {
}
}

componentWillReceiveProps(nextProps, nextContext) {
componentWillReceiveProps(nextProps: OuterPropsType, nextContext: Context) {
this.context = nextContext
const {dynamicSheet} = this.state
if (dynamicSheet) dynamicSheet.update(nextProps)
}

componentWillUpdate(nextProps, nextState) {
componentWillUpdate(nextProps: OuterPropsType, nextState: State) {
if (isThemingEnabled && this.state.theme !== nextState.theme) {
const newState = this.createState(nextState, nextProps)
this.manage(newState)
Expand All @@ -119,27 +148,29 @@ export default (stylesOrCreator, InnerComponent, options = {}) => {
}
}

componentDidUpdate(prevProps, prevState) {
componentDidUpdate(prevProps: OuterPropsType, prevState: State) {
// We remove previous dynamicSheet only after new one was created to avoid FOUC.
if (prevState.dynamicSheet !== this.state.dynamicSheet && prevState.dynamicSheet) {
this.jss.removeStyleSheet(prevState.dynamicSheet)
}
}

componentWillUnmount() {
if (this.unsubscribeId) {
if (isThemingEnabled && this.unsubscribeId) {
themeListener.unsubscribe(this.context, this.unsubscribeId)
}

this.manager.unmanage(this.state.theme)
if (this.state.dynamicSheet) {
this.state.dynamicSheet.detach()
this.jss.removeStyleSheet(this.state.dynamicSheet)
}
}

setTheme = theme => this.setState({theme})
setTheme = (theme: Theme) => this.setState({theme})
unsubscribeId: SubscriptionId
context: Context

createState({theme, dynamicSheet}, {classes: userClasses}) {
createState({theme, dynamicSheet}: State, {classes: userClasses}): State {
const contextSheetOptions = this.context[ns.sheetOptions]
if (contextSheetOptions && contextSheetOptions.disableStylesGeneration) {
return {theme, dynamicSheet, classes: {}}
Expand All @@ -161,9 +192,11 @@ export default (stylesOrCreator, InnerComponent, options = {}) => {
classNamePrefix
})
this.manager.add(theme, staticSheet)
// $FlowFixMe Cannot add random fields to instance of class StyleSheet
staticSheet[dynamicStylesNs] = getDynamicStyles(styles)
}

// $FlowFixMe Cannot access random fields on instance of class StyleSheet
const dynamicStyles = staticSheet[dynamicStylesNs]

if (dynamicStyles) {
Expand All @@ -176,6 +209,7 @@ export default (stylesOrCreator, InnerComponent, options = {}) => {
})
}

// $FlowFixMe InnerComponent can be class or stateless, the latter doesn't have a defaultProps property
const defaultClasses = InnerComponent.defaultProps
? InnerComponent.defaultProps.classes
: undefined
Expand All @@ -194,7 +228,7 @@ export default (stylesOrCreator, InnerComponent, options = {}) => {
return {theme, dynamicSheet, classes}
}

manage({theme, dynamicSheet}) {
manage({theme, dynamicSheet}: State) {
const contextSheetOptions = this.context[ns.sheetOptions]
if (contextSheetOptions && contextSheetOptions.disableStylesGeneration) {
return
Expand Down Expand Up @@ -231,8 +265,7 @@ export default (stylesOrCreator, InnerComponent, options = {}) => {

render() {
const {theme, dynamicSheet, classes} = this.state
const {innerRef, ...props} = this.props

const {innerRef, ...props}: OuterPropsType = this.props
const sheet = dynamicSheet || this.manager.get(theme)

if (injectMap.sheet && !props.sheet) props.sheet = sheet
Expand Down
6 changes: 5 additions & 1 deletion src/getDisplayName.js
@@ -1 +1,5 @@
export default Component => Component.displayName || Component.name || 'Component'
// @flow
import type {ComponentType} from 'react'

export default <P>(Component: ComponentType<P>) =>
Component.displayName || Component.name || 'Component'
1 change: 1 addition & 0 deletions src/index.js
@@ -1,3 +1,4 @@
// @flow
export {ThemeProvider, withTheme, createTheming} from 'theming'
export {default as JssProvider} from './JssProvider'
export {
Expand Down

0 comments on commit a77f94d

Please sign in to comment.