-
Notifications
You must be signed in to change notification settings - Fork 96
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
Remove singletons in favor of React context (hooks-based implementation)(backward compatible with all versions of react) #221
Changes from 26 commits
aa24736
8989282
19ab350
bc754c7
ca6340d
7d4eed1
df6b7d5
efcd432
19d0f48
00afde9
dc8894c
f25e134
61a9726
5c1c475
2a65c8a
ff18eda
e026784
0a8b12b
67e1098
94196e1
297318f
62a1b2b
8d3992b
3a8e27d
d1576d7
a179c1c
f2444bb
9b6e885
05c777d
77535fa
8cbb1bd
5f0ca91
5ac8a14
c13de70
241de38
2c4c254
9b447c6
58ce44b
d1ad51f
1c783f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,15 @@ function flush() { | |
} | ||
} | ||
|
||
// Exported until we deprecate this API completely | ||
// eslint-disable-next-line no-underscore-dangle | ||
export function _getInterface() { | ||
return styleInterface; | ||
} | ||
|
||
// Exported until we deprecate this API completely | ||
export { get as _getTheme }; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is so we can fall back to these singletons from the contextual implementation. |
||
export default { | ||
registerTheme, | ||
registerInterface, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { createContext } from 'react'; | ||
|
||
const WithStylesContext = createContext({ | ||
stylesInterface: null, | ||
stylesTheme: null, | ||
}); | ||
|
||
export default WithStylesContext; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
import React from 'react'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this file completely untouched? I.e., you just moved what was previous at |
||
import PropTypes from 'prop-types'; | ||
import hoistNonReactStatics from 'hoist-non-react-statics'; | ||
|
||
import { CHANNEL, DIRECTIONS } from 'react-with-direction/dist/constants'; | ||
import brcastShape from 'react-with-direction/dist/proptypes/brcast'; | ||
|
||
import ThemedStyleSheet from '../ThemedStyleSheet'; | ||
|
||
// Add some named exports to assist in upgrading and for convenience | ||
export const css = ThemedStyleSheet.resolveLTR; | ||
export const withStylesPropTypes = { | ||
styles: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types | ||
theme: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types | ||
css: PropTypes.func.isRequired, | ||
}; | ||
|
||
const EMPTY_STYLES = {}; | ||
const EMPTY_STYLES_FN = () => EMPTY_STYLES; | ||
|
||
const START_MARK = 'react-with-styles.createStyles.start'; | ||
const END_MARK = 'react-with-styles.createStyles.end'; | ||
|
||
function baseClass(pureComponent) { | ||
if (pureComponent) { | ||
if (!React.PureComponent) { | ||
throw new ReferenceError('withStyles() pureComponent option requires React 15.3.0 or later'); | ||
} | ||
|
||
return React.PureComponent; | ||
} | ||
|
||
return React.Component; | ||
} | ||
|
||
const contextTypes = { | ||
[CHANNEL]: brcastShape, | ||
}; | ||
|
||
const defaultDirection = DIRECTIONS.LTR; | ||
|
||
export function withStyles( | ||
styleFn, | ||
{ | ||
stylesPropName = 'styles', | ||
themePropName = 'theme', | ||
cssPropName = 'css', | ||
flushBefore = false, | ||
pureComponent = false, | ||
} = {}, | ||
) { | ||
let styleDefLTR; | ||
let styleDefRTL; | ||
let currentThemeLTR; | ||
let currentThemeRTL; | ||
const BaseClass = baseClass(pureComponent); | ||
|
||
function getResolveMethod(direction) { | ||
return direction === DIRECTIONS.LTR | ||
? ThemedStyleSheet.resolveLTR | ||
: ThemedStyleSheet.resolveRTL; | ||
} | ||
|
||
function getCurrentTheme(direction) { | ||
return direction === DIRECTIONS.LTR | ||
? currentThemeLTR | ||
: currentThemeRTL; | ||
} | ||
|
||
function getStyleDef(direction, wrappedComponentName) { | ||
const currentTheme = getCurrentTheme(direction); | ||
let styleDef = direction === DIRECTIONS.LTR | ||
? styleDefLTR | ||
: styleDefRTL; | ||
|
||
const registeredTheme = ThemedStyleSheet.get(); | ||
|
||
// Return the existing styles if they've already been defined | ||
// and if the theme used to create them corresponds to the theme | ||
// registered with ThemedStyleSheet | ||
if (styleDef && currentTheme === registeredTheme) { | ||
return styleDef; | ||
} | ||
|
||
if ( | ||
process.env.NODE_ENV !== 'production' | ||
&& typeof performance !== 'undefined' | ||
&& performance.mark !== undefined && typeof performance.clearMarks === 'function' | ||
) { | ||
performance.clearMarks(START_MARK); | ||
performance.mark(START_MARK); | ||
} | ||
|
||
const isRTL = direction === DIRECTIONS.RTL; | ||
|
||
if (isRTL) { | ||
styleDefRTL = styleFn | ||
? ThemedStyleSheet.createRTL(styleFn) | ||
: EMPTY_STYLES_FN; | ||
|
||
currentThemeRTL = registeredTheme; | ||
styleDef = styleDefRTL; | ||
} else { | ||
styleDefLTR = styleFn | ||
? ThemedStyleSheet.createLTR(styleFn) | ||
: EMPTY_STYLES_FN; | ||
|
||
currentThemeLTR = registeredTheme; | ||
styleDef = styleDefLTR; | ||
} | ||
|
||
if ( | ||
process.env.NODE_ENV !== 'production' | ||
&& typeof performance !== 'undefined' | ||
&& performance.mark !== undefined && typeof performance.clearMarks === 'function' | ||
) { | ||
performance.clearMarks(END_MARK); | ||
performance.mark(END_MARK); | ||
|
||
const measureName = `\ud83d\udc69\u200d\ud83c\udfa8 withStyles(${wrappedComponentName}) [create styles]`; | ||
|
||
performance.measure( | ||
measureName, | ||
START_MARK, | ||
END_MARK, | ||
); | ||
performance.clearMarks(measureName); | ||
} | ||
|
||
return styleDef; | ||
} | ||
|
||
function getState(direction, wrappedComponentName) { | ||
return { | ||
resolveMethod: getResolveMethod(direction), | ||
styleDef: getStyleDef(direction, wrappedComponentName), | ||
}; | ||
} | ||
|
||
return function withStylesHOC(WrappedComponent) { | ||
const wrappedComponentName = WrappedComponent.displayName | ||
|| WrappedComponent.name | ||
|| 'Component'; | ||
|
||
// NOTE: Use a class here so components are ref-able if need be: | ||
// eslint-disable-next-line react/prefer-stateless-function | ||
class WithStyles extends BaseClass { | ||
constructor(props, context) { | ||
super(props, context); | ||
|
||
const direction = this.context[CHANNEL] | ||
? this.context[CHANNEL].getState() | ||
: defaultDirection; | ||
|
||
this.state = getState(direction, wrappedComponentName); | ||
} | ||
|
||
componentDidMount() { | ||
if (this.context[CHANNEL]) { | ||
// subscribe to future direction changes | ||
this.channelUnsubscribe = this.context[CHANNEL].subscribe((direction) => { | ||
this.setState(getState(direction, wrappedComponentName)); | ||
}); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
if (this.channelUnsubscribe) { | ||
this.channelUnsubscribe(); | ||
} | ||
} | ||
|
||
render() { | ||
// As some components will depend on previous styles in | ||
// the component tree, we provide the option of flushing the | ||
// buffered styles (i.e. to a style tag) **before** the rendering | ||
// cycle begins. | ||
// | ||
// The interfaces provide the optional "flush" method which | ||
// is run in turn by ThemedStyleSheet.flush. | ||
if (flushBefore) { | ||
ThemedStyleSheet.flush(); | ||
} | ||
|
||
const { | ||
resolveMethod, | ||
styleDef, | ||
} = this.state; | ||
|
||
return ( | ||
<WrappedComponent | ||
{...this.props} | ||
{...{ | ||
[themePropName]: ThemedStyleSheet.get(), | ||
[stylesPropName]: styleDef(), | ||
[cssPropName]: resolveMethod, | ||
}} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
WithStyles.WrappedComponent = WrappedComponent; | ||
WithStyles.displayName = `withStyles(${wrappedComponentName})`; | ||
WithStyles.contextTypes = contextTypes; | ||
if (WrappedComponent.propTypes) { | ||
WithStyles.propTypes = { ...WrappedComponent.propTypes }; | ||
delete WithStyles.propTypes[stylesPropName]; | ||
delete WithStyles.propTypes[themePropName]; | ||
delete WithStyles.propTypes[cssPropName]; | ||
} | ||
if (WrappedComponent.defaultProps) { | ||
WithStyles.defaultProps = { ...WrappedComponent.defaultProps }; | ||
} | ||
|
||
return hoistNonReactStatics(WithStyles, WrappedComponent); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,72 @@ | ||||||||||||||||
import { useRef } from 'react'; | ||||||||||||||||
import { DIRECTIONS } from 'react-with-direction'; | ||||||||||||||||
|
||||||||||||||||
import withPerf from './utils/perf'; | ||||||||||||||||
import useStylesInterface from './utils/useStylesInterface'; | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these be at the same level? (within src) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't intend for these to available publicly, though I think they sitll are. |
||||||||||||||||
import useStylesTheme from './utils/useStylesTheme'; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* Hook used to derive the react-with-styles props from the provided react-with-styles | ||||||||||||||||
* theme, interface, direction, and styles function. | ||||||||||||||||
* | ||||||||||||||||
* @export | ||||||||||||||||
* @param {*} [{ direction, stylesFn, flushBefore }={}] | ||||||||||||||||
* @returns {*} { css, styles, theme } | ||||||||||||||||
*/ | ||||||||||||||||
export default function useStyles({ direction, stylesFn, flushBefore } = {}) { | ||||||||||||||||
// Get the styles interface and styles theme from context | ||||||||||||||||
const stylesInterface = useStylesInterface(); | ||||||||||||||||
const theme = useStylesTheme(); | ||||||||||||||||
|
||||||||||||||||
// Flush if specified | ||||||||||||||||
if (flushBefore && stylesInterface.flush) { | ||||||||||||||||
stylesInterface.flush(); | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// Use a cache to store the interface methods and created styles by direction. | ||||||||||||||||
// This way, if the theme and the interface don't change, we do not recalculate | ||||||||||||||||
// styles or any other interface derivations. They are effectively only calculated | ||||||||||||||||
// once per direction, until the theme or interface change. | ||||||||||||||||
const cacheRefLTR = useRef(); | ||||||||||||||||
const cacheRefRTL = useRef(); | ||||||||||||||||
const cacheRef = direction === DIRECTIONS.RTL ? cacheRefRTL : cacheRefLTR; | ||||||||||||||||
|
||||||||||||||||
// If the interface and theme haven't changed for this direction, | ||||||||||||||||
// we return all the cached values immediately. | ||||||||||||||||
if ( | ||||||||||||||||
cacheRef.current | ||||||||||||||||
&& stylesInterface | ||||||||||||||||
&& cacheRef.current.stylesInterface === stylesInterface | ||||||||||||||||
&& cacheRef.current.theme === theme | ||||||||||||||||
) { | ||||||||||||||||
return cacheRef.current; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// (Re)Create the styles props for this direction | ||||||||||||||||
const directionSelector = direction === DIRECTIONS.RTL ? 'RTL' : 'LTR'; | ||||||||||||||||
|
||||||||||||||||
// Create the themed styles from the interface's create functions | ||||||||||||||||
// with the theme and styles function provided | ||||||||||||||||
let create = stylesInterface[`create${directionSelector}`] || stylesInterface.create; | ||||||||||||||||
if (process.env.NODE_ENV !== 'production') { | ||||||||||||||||
create = withPerf('create')(create); | ||||||||||||||||
} | ||||||||||||||||
const styles = create(stylesFn ? stylesFn(theme) : {}); | ||||||||||||||||
|
||||||||||||||||
// Create the css function from the interface's resolve functions | ||||||||||||||||
let resolve = stylesInterface[`resolve${directionSelector}`] || stylesInterface.resolve; | ||||||||||||||||
if (process.env.NODE_ENV !== 'production') { | ||||||||||||||||
resolve = withPerf('resolve')(resolve); | ||||||||||||||||
} | ||||||||||||||||
const css = (...args) => resolve(args); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. :) This is why we have to do this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. react-with-styles/src/ThemedStyleSheet.js Lines 64 to 70 in 5e94b51
|
||||||||||||||||
|
||||||||||||||||
// Cache the withStyles values for this direction | ||||||||||||||||
cacheRef.current = { | ||||||||||||||||
stylesInterface, | ||||||||||||||||
theme, | ||||||||||||||||
styles, | ||||||||||||||||
css, | ||||||||||||||||
}; | ||||||||||||||||
|
||||||||||||||||
return cacheRef.current; | ||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be removed, since the helper is being used now