Skip to content

Commit 0c144c4

Browse files
committed
feat: improve context typing and docs
1 parent c576844 commit 0c144c4

File tree

3 files changed

+91
-35
lines changed

3 files changed

+91
-35
lines changed

docs/en/Provider.md

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ Uses React's context functionality to provide data to child nodes.
2121

2222
### Props
2323

24+
Signature
25+
26+
```ts
27+
interface IProviderProps {
28+
name: string;
29+
value: any;
30+
}
31+
```
32+
33+
, where
34+
2435
- `name` - context channel name
2536
- `value` - value to be broadcasted in this channel.
2637

@@ -33,30 +44,50 @@ Retrieves context value from specified channel.
3344
- `name` - provider channel to subscribe to.
3445

3546

36-
## `withContext()`
47+
## `withContext()` HOC
3748

3849
HOC that ensures your component will receive context value it subscribed to.
3950

4051
```ts
41-
withContext: (Comp: React.Component, name?: string) => React.Component;
52+
withContext: (Comp: React.Component, propName?: string, props?: IProviderProps) => React.Component;
4253
```
4354

4455
, where
4556

46-
- `Comp` - your React component.
47-
- `name` - context channel to subscribe to.
57+
- `Comp` — your React component.
58+
- `propName` — prop that will hold context value.
59+
- `props` &mdash; props passed to [`<Consumer>`](#consumer).
60+
61+
Returns a *connected* component that will have a prop named `propName` with value
62+
fetched from context.
4863

49-
Returns a *connected* component that will have a prop named `name` with value
50-
fetched from context. This component has a special prop `contextName` that
51-
your can use to overwrite the default context channel name.
64+
If `propName` is not specified or set as an empty string, the context value will be
65+
merged into component's props.
5266

5367
### Example
5468

5569
```jsx
70+
import {withContext} from 'libreact/lib/context';
71+
5672
const ColorIs = ({theme}) => <div>Color is: {theme.color}</div>;
57-
const ColorIsConnected = withContext(ColorIs, 'theme');
73+
const ColorIsConnected = withContext(ColorIs, 'theme', {name: 'theme'});
5874

5975
<Provider name="theme" value={{color: 'tomato'}}>
6076
<ColorIsConnected />
6177
</Provider>
62-
```
78+
```
79+
80+
## `@withContext` Decorator
81+
82+
Similar to [`withContenxt()`](withcontext-hoc) but a decorators that injects context value into stateful component's props.
83+
84+
```jsx
85+
import {withContext} from 'libreact/lib/context';
86+
87+
@withContext('theme', {name: 'theme'})
88+
class App extends Component {
89+
render () {
90+
return <div style={{color: this.props.theme.color}}>foo</div>;
91+
}
92+
}
93+
```
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import React, {createElement as h} from 'react';
1+
import React, {createElement as h, Component} from 'react';
22
import {storiesOf} from '@storybook/react';
33
import {action} from '@storybook/addon-actions';
44
import {linkTo} from '@storybook/addon-links';
5-
import {Provider, Consumer, withContext} from '.';
5+
import {Provider, Consumer, withContext} from '..';
66

77
const ColorIs = ({ctx}) => <div>Color is: {ctx.color}</div>;
8-
const ColorIsConnected = withContext(ColorIs, 'ctx');
9-
const ColorIsConnected2 = withContext(ColorIs);
8+
const ColorIsConnected = withContext(ColorIs, 'ctx', {name: 'ctx'});
9+
10+
@withContext('', {name: 'theme'})
11+
class Decorator1 extends Component<any, any> {
12+
render () {
13+
return <ColorIs ctx={this.props} />;
14+
}
15+
}
1016

1117
storiesOf('Context/context', module)
1218
.add('FaCC', () =>
@@ -21,8 +27,8 @@ storiesOf('Context/context', module)
2127
<ColorIsConnected />
2228
</Provider>
2329
)
24-
.add('HOC 2', () =>
25-
<Provider name="ctx" value={{color: 'papayared'}}>
26-
<ColorIsConnected2 contextName='ctx' />
30+
.add('Decorator 1', () =>
31+
<Provider name="theme" value={{color: 'tomato'}}>
32+
<Decorator1 />
2733
</Provider>
2834
);

src/context/index.ts

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1-
import {createElement as h, Component} from 'react';
2-
import * as PropTypes from 'prop-types';
3-
import {ns, noop} from '../util';
1+
import {Component} from 'react';
2+
import * as Types from 'prop-types';
3+
import {h, ns, noop} from '../util';
44
import {IObservable, observable, TObservalbeUnsub} from './observable';
55
import faccToHoc from '../util/faccToHoc';
66

77
const $$context = ns('context');
8+
89
export type TValue = {[key: string]: any};
910

10-
export class Provider extends Component<any, any> {
11+
export interface IProviderProps {
12+
children?: any;
13+
name: string;
14+
value: any;
15+
}
16+
17+
export interface IProviderState {
18+
}
19+
20+
export class Provider extends Component<IProviderProps, IProviderState> {
1121
static propTypes = {
12-
name: PropTypes.string,
13-
value: PropTypes.object.isRequired,
22+
name: Types.string,
23+
value: Types.object.isRequired,
1424
};
1525

1626
static defaultProps = {
1727
name: 'default',
1828
};
1929

2030
static childContextTypes = {
21-
[$$context]: PropTypes.object,
31+
[$$context]: Types.object,
2232
};
2333

2434
static contextTypes = {
25-
[$$context]: PropTypes.object,
35+
[$$context]: Types.object,
2636
};
2737

2838
observable: IObservable<TValue>;
@@ -64,19 +74,28 @@ export class Provider extends Component<any, any> {
6474
return {...this.parentValue, ...value};
6575
}
6676

67-
render() {
77+
render () {
6878
return this.props.children || null;
6979
}
7080
}
7181

72-
export class Consumer extends Component<any, any> {
82+
export interface IConsumerProps {
83+
children?: (value) => React.ReactElement<any>;
84+
name: string;
85+
}
86+
87+
export interface IConsumerState {
88+
value;
89+
}
90+
91+
export class Consumer extends Component<IConsumerProps, IConsumerState> {
7392
static propTypes = {
74-
name: PropTypes.string.isRequired,
75-
children: PropTypes.func.isRequired,
93+
name: Types.string.isRequired,
94+
children: Types.func.isRequired,
7695
};
7796

7897
static contextTypes = {
79-
[$$context]: PropTypes.object,
98+
[$$context]: Types.object,
8099
};
81100

82101
state = {
@@ -86,15 +105,15 @@ export class Consumer extends Component<any, any> {
86105
unsub: TObservalbeUnsub = noop;
87106

88107
observable(): IObservable<TValue> {
89-
const observable = this.context[$$context][this.props.name];
108+
const observable = this.context[$$context][this.props.name];
90109

91-
if (process.env.NODE_ENV !== 'production') {
92-
if (!observable) {
93-
throw new Error(`Context observable "${this.props.name}" not found.`);
110+
if (process.env.NODE_ENV !== 'production') {
111+
if (!observable) {
112+
throw new Error(`Context observable "${this.props.name}" not found.`);
113+
}
94114
}
95-
}
96115

97-
return observable;
116+
return observable;
98117
}
99118

100119
componentWillMount() {

0 commit comments

Comments
 (0)