Skip to content

Commit

Permalink
Merge pull request #61 from cssinjs/remove-support-for-renaming-prop
Browse files Browse the repository at this point in the history
Improve withTheme
  • Loading branch information
Henri authored Oct 27, 2018
2 parents d2866cd + 163934a commit 6a59e0f
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 61 deletions.
28 changes: 14 additions & 14 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
{
"dist/theming.js": {
"bundled": 55235,
"minified": 18967,
"gzipped": 6026
"bundled": 55879,
"minified": 19179,
"gzipped": 6108
},
"dist/theming.min.js": {
"bundled": 25422,
"minified": 9651,
"gzipped": 3505
"bundled": 26066,
"minified": 9863,
"gzipped": 3600
},
"dist/theming.cjs.js": {
"bundled": 4042,
"minified": 2495,
"gzipped": 946
"bundled": 4670,
"minified": 2763,
"gzipped": 1067
},
"dist/theming.esm.js": {
"bundled": 3670,
"minified": 2195,
"gzipped": 882,
"bundled": 4298,
"minified": 2463,
"gzipped": 1004,
"treeshaked": {
"rollup": {
"code": 1383,
"code": 1830,
"import_statements": 179
},
"webpack": {
"code": 2890
"code": 3102
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

### 2.0.0 (Unreleased)
### 2.1.0 (Unreleased)

- Align flow and TypeScript types so they export the same interfaces ([#60](https://github.com/cssinjs/theming/pull/60))
- Improve withTheme HoC, added support for innerRef and improved typings ([#61](https://github.com/cssinjs/theming/pull/61))

### 2.0.0 (2018-10-24)

- Use new React Context API ([#54](https://github.com/cssinjs/theming/pull/54))
- Switched build system to rollup for building esm, cjs and umd ([#55](https://github.com/cssinjs/theming/pull/55))
Expand Down
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,6 @@ const App = () => (
export default App;
```

#### propName

Type: `String`
Default: `theme`

The prop name of the theme.

### createTheming(context)

Function to create `ThemeProvider` and `withTheme` with custom context.
Expand Down
35 changes: 26 additions & 9 deletions src/create-with-theme.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
// @flow

import React, { type ComponentType } from 'react';
import React, { type ComponentType, type ElementRef } from 'react';
import { type Context } from 'create-react-context';
import hoist from 'hoist-non-react-statics';
import getDisplayName from 'react-display-name';

export default function createWithTheme(context: Context<{}>) {
function hoc<Props, Comp: ComponentType<Props>>(Component: Comp, propName: string = 'theme') {
function withTheme(props: Props) {
type Options = { forwardInnerRef?: boolean };
type OuterPropsType<InnerProps, InnerComponent, Theme> = $Diff<InnerProps, { theme: Theme }> & {
theme?: Theme,
innerRef?: (instance: ElementRef<InnerComponent> | null) => void
};
type InnerPropsType<Theme> = { theme: Theme };

export default function createWithTheme<Theme: {}>(context: Context<Theme>) {
return function hoc<
InnerProps: InnerPropsType<Theme>,
InnerComponent: ComponentType<InnerProps>,
OuterProps: OuterPropsType<InnerProps, InnerComponent, Theme>,
>(
Component: InnerComponent,
{ forwardInnerRef = false }: Options = {},
): ComponentType<OuterProps> {
function withTheme(props: OuterProps) {
// $FlowFixMe
const { innerRef, ...otherProps } = props;

otherProps[forwardInnerRef ? 'innerRef' : 'ref'] = innerRef;

return (
<context.Consumer>
{theme => (
<Component
{...{ [propName]: theme }}
{...props}
theme={theme}
{...otherProps}
/>
)}
</context.Consumer>
Expand All @@ -25,7 +44,5 @@ export default function createWithTheme(context: Context<{}>) {
hoist(withTheme, Component);

return withTheme;
}

return hoc;
};
}
76 changes: 51 additions & 25 deletions src/create-with-theme.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@ import TestRenderer from 'react-test-renderer';

import createWithTheme from './create-with-theme';

const Comp = () => null;
// eslint-disable-next-line no-unused-vars
const FunctionalComponent = (props: { theme: {} }) => null;

class ClassComponent extends React.Component<{ theme: {} }> {
static displayName = 'foo';

static someSpecialStatic = 'bar';

inner = true;

render() {
return null;
}
}

test('createWithTheme\'s type', (t) => {
t.true(typeof createWithTheme === 'function', 'createWithTheme should be a function');
Expand All @@ -15,73 +28,86 @@ test('createWithTheme\'s type', (t) => {
test('createWithTheme\'s result is function on its own', (t) => {
const context = createReactContext({});
const withTheme = createWithTheme(context);

t.true(typeof withTheme === 'function', 'withTheme should be a function');
});

test('should pass the default value of the context', (t) => {
const theme = {};
const context = createReactContext(theme);
const WithTheme = createWithTheme(context)(Comp);
const WithTheme = createWithTheme(context)(FunctionalComponent);
const { root } = TestRenderer.create(<WithTheme />);

t.true(root.findByType(Comp).props.theme === theme);
t.true(root.findByType(FunctionalComponent).props.theme === theme);
});


test('should pass the value of the Provider', (t) => {
const theme = { test: 'test' };
const context = createReactContext(theme);
const WithTheme = createWithTheme(context)(Comp);
const WithTheme = createWithTheme(context)(FunctionalComponent);
const { root } = TestRenderer.create((
<context.Provider value={theme}>
<WithTheme />
</context.Provider>
));

t.true(root.findByType(Comp).props.theme === theme);
t.true(root.findByType(FunctionalComponent).props.theme === theme);
});

test('should pass the theme as the specified prop', (t) => {
const theme = { test: 'test' };
test('should allow overriding the prop from the outer props', (t) => {
const theme = {};
const otherTheme = {};
const context = createReactContext(theme);
const WithTheme = createWithTheme(context)(Comp, 'outerTheme');
const WithTheme = createWithTheme(context)(FunctionalComponent);
const { root } = TestRenderer.create((
<WithTheme />
<WithTheme theme={otherTheme} />
));

t.true(root.findByType(Comp).props.outerTheme === theme);
t.true(root.findByType(FunctionalComponent).props.theme === otherTheme);
});

test('should allow overriding the prop from the outer props', (t) => {
const theme = {};
const otherTheme = {};
const context = createReactContext(theme);
const WithTheme = createWithTheme(context)(Comp);
test('innerRef should set the ref prop on the wrapped component', (t) => {
const context = createReactContext({});
const withTheme = createWithTheme(context);
let refComp = null;
const innerRef = (comp) => {
refComp = comp;
};
const WithTheme = withTheme(ClassComponent);

TestRenderer.create((
<WithTheme innerRef={innerRef} />
));

t.deepEqual(refComp !== null && refComp.inner, true);
});

test('should forward the innerRef to the wrapped component when forwardInnerRef is true', (t) => {
const context = createReactContext({});
const WithTheme = createWithTheme(context)(FunctionalComponent, { forwardInnerRef: true });
const innerRef = () => undefined;
const { root } = TestRenderer.create((
<WithTheme theme={otherTheme} />
<WithTheme innerRef={innerRef} />
));

t.true(root.findByType(Comp).props.theme === otherTheme);
t.true(root.findByType(FunctionalComponent).props.innerRef === innerRef);
});

test('withTheme(Comp) hoists non-react static class properties', (t) => {
const context = createReactContext({});
const withTheme = createWithTheme(context);
class ExampleComponent extends React.Component<{}> {
static displayName = 'foo';
const WithTheme = withTheme(ClassComponent);

static someSpecialStatic = 'bar';
}
const ComponentWithTheme = withTheme(ExampleComponent);
t.deepEqual(
ComponentWithTheme.displayName,
WithTheme.displayName,
'WithTheme(foo)',
'withTheme(Comp) should not hoist react static properties',
);
t.deepEqual(
// $FlowFixMe: Need to find a better way to type the hoist-non-react-statics
ComponentWithTheme.someSpecialStatic,
ExampleComponent.someSpecialStatic,
WithTheme.someSpecialStatic,
ClassComponent.someSpecialStatic,
'withTheme(Comp) should hoist non-react static properties',
);
});
17 changes: 12 additions & 5 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import * as React from 'react';
import { Context } from 'create-react-context';

type DefaultTheme = {};
type DefaultTheme = object;

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

type WithThemeFactory<Theme> = <Props extends { theme: Theme }>(
comp: React.ComponentType<Props>,
name: string,
) => React.ComponentType<Omit<Props, { theme: Theme }>>;
type WithThemeFactory<Theme> = <
InnerProps extends { theme: Theme },
InnerComponent extends React.ComponentType<InnerProps>,
OuterProps extends Omit<InnerProps, { theme: Theme }> & {
theme?: Theme,
innerRef?: (ref: InnerComponent | null) => void,
},
>(
comp: InnerComponent,
options: { forwardInnerRef: boolean },
) => React.ComponentType<OuterProps>;

type ThemeProviderFactory<Theme> = React.ComponentType<{
theme: Theme | ((outerTheme: object) => Theme),
Expand Down

0 comments on commit 6a59e0f

Please sign in to comment.