Skip to content

Commit

Permalink
feat: improve context typing and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Feb 7, 2018
1 parent c576844 commit 0c144c4
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 35 deletions.
49 changes: 40 additions & 9 deletions docs/en/Provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ Uses React's context functionality to provide data to child nodes.

### Props

Signature

```ts
interface IProviderProps {
name: string;
value: any;
}
```

, where

- `name` - context channel name
- `value` - value to be broadcasted in this channel.

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


## `withContext()`
## `withContext()` HOC

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

```ts
withContext: (Comp: React.Component, name?: string) => React.Component;
withContext: (Comp: React.Component, propName?: string, props?: IProviderProps) => React.Component;
```

, where

- `Comp` - your React component.
- `name` - context channel to subscribe to.
- `Comp` — your React component.
- `propName` — prop that will hold context value.
- `props` &mdash; props passed to [`<Consumer>`](#consumer).

Returns a *connected* component that will have a prop named `propName` with value
fetched from context.

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

### Example

```jsx
import {withContext} from 'libreact/lib/context';

const ColorIs = ({theme}) => <div>Color is: {theme.color}</div>;
const ColorIsConnected = withContext(ColorIs, 'theme');
const ColorIsConnected = withContext(ColorIs, 'theme', {name: 'theme'});

<Provider name="theme" value={{color: 'tomato'}}>
<ColorIsConnected />
</Provider>
```
```

## `@withContext` Decorator

Similar to [`withContenxt()`](withcontext-hoc) but a decorators that injects context value into stateful component's props.

```jsx
import {withContext} from 'libreact/lib/context';

@withContext('theme', {name: 'theme'})
class App extends Component {
render () {
return <div style={{color: this.props.theme.color}}>foo</div>;
}
}
```
20 changes: 13 additions & 7 deletions src/context/story.tsx → src/context/__story__/story.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import React, {createElement as h} from 'react';
import React, {createElement as h, Component} from 'react';
import {storiesOf} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {linkTo} from '@storybook/addon-links';
import {Provider, Consumer, withContext} from '.';
import {Provider, Consumer, withContext} from '..';

const ColorIs = ({ctx}) => <div>Color is: {ctx.color}</div>;
const ColorIsConnected = withContext(ColorIs, 'ctx');
const ColorIsConnected2 = withContext(ColorIs);
const ColorIsConnected = withContext(ColorIs, 'ctx', {name: 'ctx'});

@withContext('', {name: 'theme'})
class Decorator1 extends Component<any, any> {
render () {
return <ColorIs ctx={this.props} />;
}
}

storiesOf('Context/context', module)
.add('FaCC', () =>
Expand All @@ -21,8 +27,8 @@ storiesOf('Context/context', module)
<ColorIsConnected />
</Provider>
)
.add('HOC 2', () =>
<Provider name="ctx" value={{color: 'papayared'}}>
<ColorIsConnected2 contextName='ctx' />
.add('Decorator 1', () =>
<Provider name="theme" value={{color: 'tomato'}}>
<Decorator1 />
</Provider>
);
57 changes: 38 additions & 19 deletions src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import {createElement as h, Component} from 'react';
import * as PropTypes from 'prop-types';
import {ns, noop} from '../util';
import {Component} from 'react';
import * as Types from 'prop-types';
import {h, ns, noop} from '../util';
import {IObservable, observable, TObservalbeUnsub} from './observable';
import faccToHoc from '../util/faccToHoc';

const $$context = ns('context');

export type TValue = {[key: string]: any};

export class Provider extends Component<any, any> {
export interface IProviderProps {
children?: any;
name: string;
value: any;
}

export interface IProviderState {
}

export class Provider extends Component<IProviderProps, IProviderState> {
static propTypes = {
name: PropTypes.string,
value: PropTypes.object.isRequired,
name: Types.string,
value: Types.object.isRequired,
};

static defaultProps = {
name: 'default',
};

static childContextTypes = {
[$$context]: PropTypes.object,
[$$context]: Types.object,
};

static contextTypes = {
[$$context]: PropTypes.object,
[$$context]: Types.object,
};

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

render() {
render () {
return this.props.children || null;
}
}

export class Consumer extends Component<any, any> {
export interface IConsumerProps {
children?: (value) => React.ReactElement<any>;
name: string;
}

export interface IConsumerState {
value;
}

export class Consumer extends Component<IConsumerProps, IConsumerState> {
static propTypes = {
name: PropTypes.string.isRequired,
children: PropTypes.func.isRequired,
name: Types.string.isRequired,
children: Types.func.isRequired,
};

static contextTypes = {
[$$context]: PropTypes.object,
[$$context]: Types.object,
};

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

observable(): IObservable<TValue> {
const observable = this.context[$$context][this.props.name];
const observable = this.context[$$context][this.props.name];

if (process.env.NODE_ENV !== 'production') {
if (!observable) {
throw new Error(`Context observable "${this.props.name}" not found.`);
if (process.env.NODE_ENV !== 'production') {
if (!observable) {
throw new Error(`Context observable "${this.props.name}" not found.`);
}
}
}

return observable;
return observable;
}

componentWillMount() {
Expand Down

0 comments on commit 0c144c4

Please sign in to comment.