Skip to content

Commit

Permalink
fix: make useStyles work in strict mode (cssinjs#1648)
Browse files Browse the repository at this point in the history
  • Loading branch information
ernestostifano committed Jul 13, 2023
1 parent 3e3290d commit 51b2d6b
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 39 deletions.
24 changes: 12 additions & 12 deletions packages/react-jss/.size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
{
"react-jss.js": {
"bundled": 126673,
"minified": 46590,
"gzipped": 15618
"bundled": 129153,
"minified": 46606,
"gzipped": 15611
},
"react-jss.min.js": {
"bundled": 93875,
"minified": 36601,
"gzipped": 12819
"bundled": 96355,
"minified": 36617,
"gzipped": 12813
},
"react-jss.cjs.js": {
"bundled": 22967,
"minified": 9723,
"gzipped": 3237
"bundled": 25351,
"minified": 9749,
"gzipped": 3229
},
"react-jss.esm.js": {
"bundled": 20972,
"minified": 8169,
"gzipped": 3014,
"bundled": 23350,
"minified": 8189,
"gzipped": 3010,
"treeshaked": {
"rollup": {
"code": 442,
Expand Down
76 changes: 61 additions & 15 deletions packages/react-jss/src/createUseStyles.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import React, {useEffect, useLayoutEffect, useContext, useRef, useMemo, useDebugValue} from 'react'
import React, {
useEffect,
useLayoutEffect,
useContext,
useRef,
useMemo,
useDebugValue,
useId
} from 'react'
import {ThemeContext as DefaultThemeContext} from 'theming'

import JssContext from './JssContext'
import {
createStyleSheet,
addDynamicRules,
updateDynamicRules,
removeDynamicRules
removeDynamicRules,
getDynamicRulesClassNames
} from './utils/sheets'
import getSheetIndex from './utils/getSheetIndex'
import {manageSheet, unmanageSheet} from './utils/managers'
Expand All @@ -21,6 +30,15 @@ function getUseInsertionEffect(isSSR) {

const noTheme = {}

// TODO: CREATE getUseId FUNCTION SO useId IS INTRODUCED WITHOUT BREAKING CHANGES AND KEEPING COMPATIBILITY WITH OLDER REACT VERSIONS
// TODO: CHECK THAT ALL KEY/CLASSNAME CUSTOMIZATION FLOWS CONTINUE TO WORK (createGenerateId, generateId, classNamePrefix, etc.)
// TODO: UPDATE TYPE DEFINITIONS
// TODO: REPLICATE ORIGINAL createGenerateId AS MUCH AS POSSIBLE (INCLUDE JSS ID?)

const createGenerateId = () => (rule) => `${rule.key}`

const generateId = createGenerateId()

/*
* *** NOTE ABOUT STRICT MODE AND REACT 18+ ***
*
Expand Down Expand Up @@ -75,14 +93,21 @@ const createUseStyles = (styles, options = {}) => {
const context = useContext(JssContext)
const theme = useTheme(data && data.theme)

const [sheet, dynamicRules] = useMemo(() => {
const instanceId = useId().replaceAll(':', '-')

const dynamicRulesRef = useRef(null)

const sheet = useMemo(() => {
const newSheet = createStyleSheet({
context,
styles,
name,
theme,
index,
sheetOptions
sheetOptions: {
...sheetOptions,
generateId
}
})

if (newSheet && context.isSSR) {
Expand All @@ -95,13 +120,35 @@ const createUseStyles = (styles, options = {}) => {
})
}

return [newSheet, newSheet ? addDynamicRules(newSheet, data) : null]
return newSheet
}, [context, theme])

const dynamicRulesClassNames = useMemo(
() => getDynamicRulesClassNames(sheet, instanceId),
[sheet, instanceId]
)

getUseInsertionEffect(context.isSSR)(() => {
let dynamicRules = null

if (sheet) {
dynamicRules = addDynamicRules(sheet, data, dynamicRulesClassNames)
}

dynamicRulesRef.current = dynamicRules

return () => {
if (dynamicRules) {
removeDynamicRules(sheet, dynamicRules)
dynamicRulesRef.current = null
}
}
}, [sheet])

getUseInsertionEffect(context.isSSR)(() => {
// 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 (sheet && dynamicRulesRef.current && !isFirstMount.current) {
updateDynamicRules(data, sheet, dynamicRulesRef.current)
}
}, [data])

Expand All @@ -123,18 +170,17 @@ const createUseStyles = (styles, options = {}) => {
sheet,
theme
})

// when sheet changes, remove related dynamic rules
if (dynamicRules) {
removeDynamicRules(sheet, dynamicRules)
}
}
}
}, [sheet]) // TODO: ADD dynamicRules?
}, [sheet])

const classes = useMemo(
() => (sheet && dynamicRules ? getSheetClasses(sheet, dynamicRules) : emptyObject), // TODO: REMOVE dynamicRules FROM CHECK?
[sheet, dynamicRules]
() =>
// TODO: REMOVE dynamicRulesClassNames FROM CHECK?
sheet && dynamicRulesClassNames
? getSheetClasses(sheet, dynamicRulesClassNames)
: emptyObject,
[sheet, dynamicRulesClassNames]
)

useDebugValue(classes)
Expand Down
2 changes: 2 additions & 0 deletions packages/react-jss/src/flow-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ export type ThemedStyles<Theme, Data = {}> = (theme: Theme) => {
}

export type Styles<Theme> = StaticStyles | ThemedStyles<Theme>

export type DynamicRulesClassNames = {[string]: {key: string, id: string}}
15 changes: 11 additions & 4 deletions packages/react-jss/src/utils/getSheetClasses.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {getMeta} from './sheetsMeta'

const getSheetClasses = (sheet, dynamicRules) => {
if (!dynamicRules) {
const getSheetClasses = (sheet, dynamicRulesClassNames) => {
if (!dynamicRulesClassNames) {
return sheet.classes
}

Expand All @@ -12,11 +12,18 @@ const getSheetClasses = (sheet, dynamicRules) => {
}

const classes = {}

for (const key in meta.styles) {
classes[key] = sheet.classes[key]

if (key in dynamicRules) {
classes[key] += ` ${sheet.classes[dynamicRules[key].key]}`
if (key in dynamicRulesClassNames) {
const k = dynamicRulesClassNames[key].key

if (sheet.classes[k]) {
classes[key] += ` ${sheet.classes[k]}`
} else {
classes[key] += ` ${dynamicRulesClassNames[key].id}`
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/react-jss/src/utils/getSheetClasses.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import type {StyleSheet} from 'jss'
import type {DynamicRules, Classes} from '../flow-types'
import type {DynamicRulesClassNames, Classes} from '../flow-types'

type GetSheetClasses = (StyleSheet, DynamicRules | void) => Classes
type GetSheetClasses = (StyleSheet, DynamicRulesClassNames | void) => Classes

declare export default GetSheetClasses
28 changes: 24 additions & 4 deletions packages/react-jss/src/utils/sheets.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,10 @@ export const updateDynamicRules = (data, sheet, rules) => {
}
}

// FIXME: DURING STRICT MODE DOUBLE RENDER THIS FUNCTION IS ADDING DYNAMIC RULES THAT ALREADY EXIST AND IS ALSO RETURNING AN OBJECT WITH THE RULES ADDED DURING SECOND RENDER, SO RULES ADDED DURING FIRST RENDER ARE NEVER REMOVED
export const addDynamicRules = (sheet, data) => {
export const addDynamicRules = (sheet, data, classNames) => {
const meta = getMeta(sheet)

if (!meta) {
if (!meta || !classNames) {
return undefined
}

Expand All @@ -104,7 +103,7 @@ export const addDynamicRules = (sheet, data) => {
// Loop over each dynamic rule and add it to the stylesheet
for (const key in meta.dynamicStyles) {
const initialRuleCount = sheet.rules.index.length
const originalRule = sheet.addRule(key, meta.dynamicStyles[key])
const originalRule = sheet.addRule(classNames[key].key, meta.dynamicStyles[key])

// Loop through all created rules, fixes updating dynamic rules
for (let i = initialRuleCount; i < sheet.rules.index.length; i++) {
Expand All @@ -119,3 +118,24 @@ export const addDynamicRules = (sheet, data) => {

return rules
}

export const getDynamicRulesClassNames = (sheet, instanceId) => {
const meta = getMeta(sheet)

if (!meta) {
return undefined
}

const classNames = {}

for (const key in meta.dynamicStyles) {
const k = `${key}-d${instanceId}i`

classNames[key] = {
key: k,
id: sheet.options.generateId({key: k}, sheet)
}
}

return classNames
}
13 changes: 11 additions & 2 deletions packages/react-jss/src/utils/sheets.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import {type StyleSheetFactoryOptions} from 'jss'
import type {StyleSheet} from 'jss'
import type {Context, DynamicRules, Styles} from '../flow-types'
import type {Context, DynamicRules, Styles, DynamicRulesClassNames} from '../flow-types'

type Options<Theme> = {
context: Context,
Expand All @@ -18,4 +18,13 @@ declare export function removeDynamicRules(sheet: StyleSheet, rules: DynamicRule

declare export function updateDynamicRules(data: any, sheet: StyleSheet, rules: DynamicRules): void

declare export function addDynamicRules(sheet: StyleSheet, data: any): DynamicRules | void
declare export function addDynamicRules(
sheet: StyleSheet,
data: any,
classNames: DynamicRulesClassNames
): DynamicRules | void

declare export function getDynamicRulesClassNames(
sheet: StyleSheet,
instanceId: string
): DynamicRulesClassNames | void

0 comments on commit 51b2d6b

Please sign in to comment.