diff --git a/packages/react-jss/.size-snapshot.json b/packages/react-jss/.size-snapshot.json index 699840457..f1838936c 100644 --- a/packages/react-jss/.size-snapshot.json +++ b/packages/react-jss/.size-snapshot.json @@ -1,23 +1,23 @@ { "dist/react-jss.js": { - "bundled": 169946, - "minified": 58911, - "gzipped": 19311 + "bundled": 171071, + "minified": 59339, + "gzipped": 19438 }, "dist/react-jss.min.js": { - "bundled": 112923, - "minified": 42170, - "gzipped": 14335 + "bundled": 114048, + "minified": 42598, + "gzipped": 14460 }, "dist/react-jss.cjs.js": { - "bundled": 26398, - "minified": 11532, - "gzipped": 3759 + "bundled": 27447, + "minified": 12023, + "gzipped": 3885 }, "dist/react-jss.esm.js": { - "bundled": 24901, - "minified": 10301, - "gzipped": 3602, + "bundled": 25941, + "minified": 10783, + "gzipped": 3728, "treeshaked": { "rollup": { "code": 1838, diff --git a/packages/react-jss/src/createUseStyles.js b/packages/react-jss/src/createUseStyles.js index c9176ff90..ed0743a04 100644 --- a/packages/react-jss/src/createUseStyles.js +++ b/packages/react-jss/src/createUseStyles.js @@ -20,6 +20,13 @@ const useEffectOrLayoutEffect = isInBrowser ? React.useLayoutEffect : React.useE const noTheme = {} +const reducer = (prevState, action) => { + if (action.type === 'updateSheet') { + return action.payload + } + return prevState +} + const createUseStyles = (styles: Styles, options?: HookOptions = {}) => { const {index = getSheetIndex(), theming, name, ...sheetOptions} = options const ThemeContext = (theming && theming.context) || DefaultThemeContext @@ -35,67 +42,101 @@ const createUseStyles = (styles: Styles, options?: HookOptions const context = React.useContext(JssContext) const theme = useTheme() - const [sheet, dynamicRules] = React.useMemo( + const [state, dispatch] = React.useReducer(reducer, null, () => { + const sheet = createStyleSheet({ + context, + styles, + name, + theme, + index, + sheetOptions + }) + + let dynamicRules + let classes + if (sheet) { + if (context.registry) { + context.registry.add(sheet) + } + dynamicRules = addDynamicRules(sheet, data) + classes = getSheetClasses(sheet, dynamicRules) + } + + return { + sheet, + dynamicRules, + classes: classes || {} + } + }) + React.useMemo( + () => { + if (!isFirstMount.current) { + const newSheet = createStyleSheet({ + context, + styles, + name, + theme, + index, + sheetOptions + }) + const newDynamicRules = newSheet && addDynamicRules(newSheet, data) + const newClasses = newSheet ? getSheetClasses(newSheet, newDynamicRules) : {} + + dispatch({ + type: 'updateSheet', + payload: { + sheet: newSheet, + dynamicRules: newDynamicRules, + classes: newClasses + } + }) + } + }, + [theme, context] + ) + useEffectOrLayoutEffect( () => { - const newSheet = createStyleSheet({ - context, - styles, - name, - theme, - index, - sheetOptions - }) - - const newDynamicRules = newSheet ? addDynamicRules(newSheet, data) : null - - if (newSheet) { + if (state.sheet) { manageSheet({ index, context, - sheet: newSheet, + sheet: state.sheet, theme }) } - return [newSheet, newDynamicRules] + return () => { + const {sheet, dynamicRules} = state + + if (!sheet) return + + unmanageSheet({ + index, + context, + sheet, + theme + }) + + if (dynamicRules) { + removeDynamicRules(sheet, dynamicRules) + } + } }, - [context, theme] + [state.sheet] ) useEffectOrLayoutEffect( () => { // We only need to update the rules on a subsequent update and not in the first mount - if (sheet && dynamicRules && !isFirstMount.current) { - updateDynamicRules(data, sheet, dynamicRules) + if (state.sheet && state.dynamicRules && !isFirstMount.current) { + updateDynamicRules(data, state.sheet, state.dynamicRules) } }, [data] ) - useEffectOrLayoutEffect( - () => - // cleanup only - () => { - if (sheet) { - unmanageSheet({ - index, - context, - sheet, - theme - }) - } - - if (sheet && dynamicRules) { - removeDynamicRules(sheet, dynamicRules) - } - }, - [sheet] - ) - - const classes = sheet && dynamicRules ? getSheetClasses(sheet, dynamicRules) : {} - // $FlowFixMe - React.useDebugValue(classes) + React.useDebugValue(state.classes) // $FlowFixMe React.useDebugValue(theme === noTheme ? 'No theme' : theme) @@ -103,7 +144,7 @@ const createUseStyles = (styles: Styles, options?: HookOptions isFirstMount.current = false }) - return classes + return state.classes } } diff --git a/packages/react-jss/src/createUseStyles.test.js b/packages/react-jss/src/createUseStyles.test.js index 803161fd9..7136ed404 100644 --- a/packages/react-jss/src/createUseStyles.test.js +++ b/packages/react-jss/src/createUseStyles.test.js @@ -1,16 +1,72 @@ /* eslint-disable react/prop-types */ -import createUseStyles from './createUseStyles' -import createBasicTests from '../test-utils/createBasicTests' -const createStyledComponent = (styles, options) => { - const useStyles = createUseStyles(styles, options) - const Comp = props => { - useStyles(props) - return null - } - return Comp +import expect from 'expect.js' +import React from 'react' +import TestRenderer from 'react-test-renderer' +import {stripIndent} from 'common-tags' + +import {SheetsRegistry, JssProvider, ThemeProvider, createUseStyles} from '.' + +const createGenerateId = () => { + let counter = 0 + return rule => `${rule.key}-${counter++}` +} + +const theme: Object = { + background: 'yellow', + background2: 'red' } describe('React-JSS: createUseStyles', () => { - createBasicTests({createStyledComponent}) + it('should render multiple elements with applied media query', () => { + const registry = new SheetsRegistry() + const useStyles = createUseStyles(themeObj => ({ + wrapper: () => ({ + padding: 40, + background: themeObj.background, + textAlign: 'left', + '@media (min-width: 1024px)': { + backgroundColor: themeObj.background2 + } + }) + })) + + const Comp = () => { + const classes = useStyles(theme) + return
+ } + + const a = [1, 2] + TestRenderer.create( + + + {a.map(item => ( + + ))} + + + ) + expect(registry.toString()).to.be(stripIndent` + .wrapper-0 {} + .wrapper-d0-1 { + padding: 40px; + background: yellow; + text-align: left; + } + @media (min-width: 1024px) { + .wrapper-d0-1 { + background-color: red; + } + } + .wrapper-d1-2 { + padding: 40px; + background: yellow; + text-align: left; + } + @media (min-width: 1024px) { + .wrapper-d1-2 { + background-color: red; + } + }`) + }) })