Skip to content
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

Move the hooks implementation to its own HOC and re-implement withStyles with context as a class component #225

Merged
merged 11 commits into from
Aug 29, 2019
10 changes: 10 additions & 0 deletions src/WithStylesContext.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createContext } from 'react';
import PropTypes from 'prop-types';
import { DIRECTIONS } from 'react-with-direction';

function detectAndCreateContext(defaultValue) {
if (createContext) {
Expand All @@ -18,6 +20,14 @@ function detectAndCreateContext(defaultValue) {
const WithStylesContext = detectAndCreateContext({
stylesInterface: null,
stylesTheme: null,
direction: null,
});

WithStylesContext.Provider.propTypes = {
stylesInterface: PropTypes.object, // eslint-disable-line react/forbid-prop-types
stylesTheme: PropTypes.object, // eslint-disable-line react/forbid-prop-types
direction: PropTypes.oneOf([DIRECTIONS.LTR, DIRECTIONS.RTL]),
};

export default WithStylesContext;
export { DIRECTIONS };
5 changes: 5 additions & 0 deletions src/hooks/detectHooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useContext, useRef } from 'react';

export default function detectHooks() {
return useContext && useRef;
}
23 changes: 14 additions & 9 deletions src/useStyles.js → src/hooks/useStyles.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { useContext, useRef } from 'react';
import { DIRECTIONS } from 'react-with-direction';

import withPerf from './utils/perf';
import WithStylesContext from './WithStylesContext';
import { _getInterface, _getTheme } from './ThemedStyleSheet';
import withPerf from '../utils/perf';
import WithStylesContext, { DIRECTIONS } from '../WithStylesContext';
import { _getInterface, _getTheme } from '../ThemedStyleSheet';
import detectHooks from './detectHooks';

/**
* Hook used to derive the react-with-styles props from the provided react-with-styles
* theme, interface, direction, and styles function.
*
* Note that this implementation does not use caching for stylesFn(theme) results, so only
* use this if you are not relying on this performance optimization.
*
* @export
* @param {*} [{ direction, stylesFn, flushBefore }={}]
* @returns {*} { css, styles, theme }
* @param {Object} [{ stylesFn, flushBefore }={}]
* @returns {Object} { css, styles, theme }
*/
export default function useStyles({ direction, stylesFn, flushBefore } = {}) {
if (!useContext || !useRef) {
export default function useStyles({ stylesFn, flushBefore } = {}) {
if (!detectHooks()) {
throw new ReferenceError('useStyles() requires React 16.8 or later');
}

// Get the styles interface and styles theme from context
let { stylesInterface, stylesTheme: theme } = useContext(WithStylesContext);
const context = useContext(WithStylesContext);
let { stylesInterface, stylesTheme: theme } = context;
const { direction } = context;

// Fallback to the singleton implementation
stylesInterface = stylesInterface || _getInterface();
Expand Down
49 changes: 49 additions & 0 deletions src/providers/WithStylesDirectionAdapter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import withDirection, { withDirectionPropTypes } from 'react-with-direction';
import WithStylesContext from '../WithStylesContext';

const propTypes = {
...withDirectionPropTypes,
children: PropTypes.node,
};

const defaultProps = {
children: null,
};

/**
* Sets the direction in WithStylesContext to the direction provided by `react-with-direction`,
* but maintains the theme and interface already provided
*
* @class WithStylesDirectionAdapter
* @extends {React.Component}
*/
// We need this to be a component to use `Component.contextType`
// eslint-disable-next-line react/prefer-stateless-function
class WithStylesDirectionAdapter extends React.Component {
render() {
const { direction, children } = this.props;
const { stylesInterface, stylesTheme } = this.context;

return (
<WithStylesContext.Provider value={{ stylesInterface, stylesTheme, direction }}>
{children}
</WithStylesContext.Provider>
);
}
}

WithStylesDirectionAdapter.propTypes = propTypes;
WithStylesDirectionAdapter.defaultProps = defaultProps;
WithStylesDirectionAdapter.contextType = WithStylesContext;

// eslint-disable-next-line no-underscore-dangle
const _WithStylesDirectionAdapter = withDirection(WithStylesDirectionAdapter);

// Have to remove the contextType the withDirection component hoists because
// it's using an old version of hoist-non-react-statics that copies it over
// TODO: remove this once withDirection updates hoist-non-react-statics
delete _WithStylesDirectionAdapter.contextType;

export default _WithStylesDirectionAdapter;
40 changes: 40 additions & 0 deletions src/providers/WithStylesInterfaceProvider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import WithStylesContext from '../WithStylesContext';

const propTypes = {
stylesInterface: PropTypes.obj.isRequired,
children: PropTypes.node,
};

const defaultProps = {
children: null,
};

/**
* Changes the interface in WithStylesContext, but maintains the theme and direction
* already provided
*
* @class WithStylesInterfaceProvider
* @extends {React.Component}
*/
// We need this to be a component to use `Component.contextType`:
// eslint-disable-next-line react/prefer-stateless-function
class WithStylesInterfaceProvider extends React.Component {
render() {
const { stylesInterface, children } = this.props;
const { stylesTheme, direction } = this.context;

return (
<WithStylesContext.Provider value={{ stylesInterface, stylesTheme, direction }}>
{children}
</WithStylesContext.Provider>
);
}
}

WithStylesInterfaceProvider.propTypes = propTypes;
WithStylesInterfaceProvider.defaultProps = defaultProps;
WithStylesInterfaceProvider.contextType = WithStylesContext;

export default WithStylesInterfaceProvider;
40 changes: 40 additions & 0 deletions src/providers/WithStylesThemeProvider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import WithStylesContext from '../WithStylesContext';

const propTypes = {
theme: PropTypes.obj.isRequired,
children: PropTypes.node,
};

const defaultProps = {
children: null,
};

/**
* Changes the theme in WithStylesContext, but maintains the interface and direction
* already provided
*
* @class WithStylesThemeProvider
* @extends {React.Component}
*/
// We need this to be a component to use `Component.contextType`
// eslint-disable-next-line react/prefer-stateless-function
class WithStylesThemeProvider extends React.Component {
render() {
const { theme, children } = this.props;
const { stylesInterface, direction } = this.context;

return (
<WithStylesContext.Provider value={{ stylesInterface, stylesTheme: theme, direction }}>
{children}
</WithStylesContext.Provider>
);
}
}

WithStylesThemeProvider.propTypes = propTypes;
WithStylesThemeProvider.defaultProps = defaultProps;
WithStylesThemeProvider.contextType = WithStylesContext;

export default WithStylesThemeProvider;
5 changes: 0 additions & 5 deletions src/utils/detectHooks.js

This file was deleted.

4 changes: 4 additions & 0 deletions src/utils/emptyStylesFn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const EMPTY_STYLES = {};
const EMPTY_STYLES_FN = () => EMPTY_STYLES;

export default EMPTY_STYLES_FN;
19 changes: 0 additions & 19 deletions src/utils/useBroadcast.js

This file was deleted.

21 changes: 0 additions & 21 deletions src/withStyles.js

This file was deleted.