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

withQueries decorator syntax #9

Merged
merged 1 commit into from
Oct 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"presets": ["airbnb", "stage-1"]
"presets": ["airbnb", "stage-1"],
"plugins": [
"transform-decorators-legacy"
]
}
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ test/
*.png
.npmignore
.babelrc
.travis.yml
.travis.yml
jsconfig.json
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ const AppContainer = connect(App, {
});
```

### `withQueries(config)`
`withQueries` is like `connect`, but designed to be used as a decorator. If you have enabled the decorator syntax in your project, instead of using `connect` like above, you can do the following:
```js

import {withQueries} from 'react-hz'

@withQueries({
subscriptions: {
// ...
},
mutations: {
// ...
}
})
class MyComponent extends Component {
// ...
}
```



### Subscriptions

`subscriptions` is a map of subscription names to query functions. Data behind query is available as a prop with the same name in React component. Query function receives Horizon `hz` function which should be used to construct a query using Horizon's Collection API and props object which is being passed into container component.
Expand Down
5 changes: 5 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions" : {
"experimentalDecorators": true
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"type": "git",
"url": "https://github.com/roman01la/react-horizon.git"
},
"bugs" : {
"bugs": {
"url": "https://github.com/roman01la/react-horizon/issues"
},
"homepage": "https://github.com/roman01la/react-horizon",
Expand All @@ -33,9 +33,10 @@
},
"devDependencies": {
"babel-cli": "^6.10.1",
"babel-register": "^6.9.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-airbnb": "^2.0.0",
"babel-preset-stage-1": "^6.5.0",
"babel-register": "^6.9.0",
"chai": "^3.5.0",
"enzyme": "^2.3.0",
"jsdom": "^9.2.1",
Expand Down
110 changes: 58 additions & 52 deletions src/connect.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,68 @@
import React, { Component, PropTypes } from 'react';
import shallowequal from 'shallowequal';

export default function connect(ReactComponent, { subscriptions = {}, mutations = {} }) {
return class extends Component {
static contextTypes = {
hz: PropTypes.func
}
constructor(props, context) {
export function withQueries({ subscriptions = {}, mutations = {} }) {
return function(ReactComponent) {
return class extends Component {
static contextTypes = {
hz: PropTypes.func
}
constructor(props, context) {

super(props, context);
super(props, context);

this._subscriptions = [];
this._mutations = {};
this._subscriptions = [];
this._mutations = {};

this.state = Object.keys(subscriptions)
.reduce((initialState, qname) => {
initialState[qname] = [];
return initialState;
}, {});
this.state = Object.keys(subscriptions)
.reduce((initialState, qname) => {
initialState[qname] = [];
return initialState;
}, {});

this._subscribe = this._subscribe.bind(this);
this._unsubscribe = this._unsubscribe.bind(this);
this._createMutations = this._createMutations.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return shallowequal(nextProps, this.props) === false ||
shallowequal(nextState, this.state) === false;
}
componentWillReceiveProps(nextProps) {
this._unsubscribe(this._subscriptions);
this._subscribe(this.context.hz, nextProps);
}
componentWillMount() {
this._subscribe(this.context.hz, this.props);
this._createMutations(this.context.hz);
}
componentWillUnmount() {
this._unsubscribe(this._subscriptions);
}
_subscribe(hz, props) {
Object.keys(subscriptions)
.forEach((qname) => {
const q = subscriptions[qname];
const subscription = q(hz, props).watch().subscribe((data) => this.setState({ [qname]: data }));
this._subscriptions.push(subscription);
});
}
_unsubscribe(subscriptions) {
subscriptions.forEach((q) => q.unsubscribe());
}
_createMutations(hz) {
Object.keys(mutations)
.forEach((mname) => {
this._mutations[mname] = mutations[mname](hz);
});
}
render() {
return <ReactComponent {...this.props} {...this.state} {...this._mutations} />;
this._subscribe = this._subscribe.bind(this);
this._unsubscribe = this._unsubscribe.bind(this);
this._createMutations = this._createMutations.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return shallowequal(nextProps, this.props) === false ||
shallowequal(nextState, this.state) === false;
}
componentWillReceiveProps(nextProps) {
this._unsubscribe(this._subscriptions);
this._subscribe(this.context.hz, nextProps);
}
componentWillMount() {
this._subscribe(this.context.hz, this.props);
this._createMutations(this.context.hz);
}
componentWillUnmount() {
this._unsubscribe(this._subscriptions);
}
_subscribe(hz, props) {
Object.keys(subscriptions)
.forEach((qname) => {
const q = subscriptions[qname];
const subscription = q(hz, props).watch().subscribe((data) => this.setState({ [qname]: data }));
this._subscriptions.push(subscription);
});
}
_unsubscribe(subscriptions) {
subscriptions.forEach((q) => q.unsubscribe());
}
_createMutations(hz) {
Object.keys(mutations)
.forEach((mname) => {
this._mutations[mname] = mutations[mname](hz);
});
}
render() {
return <ReactComponent {...this.props} {...this.state} {...this._mutations} />;
}
}
}
}

export default function connect(ReactComponent, options) {
return withQueries(options)(ReactComponent);
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as Horizon } from '@horizon/client/dist/horizon';
export { default as connect } from './connect';
export { default as connect, withQueries } from './connect';
export { default as HorizonProvider } from './provider';
export { default as HorizonRoute } from './route';
55 changes: 55 additions & 0 deletions test/withQueries.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';
import { expect } from 'chai';
import { withQueries, HorizonProvider } from '../src/index';
import { Horizon } from './utils';

describe('withQueries', () => {

it('should create container component with 2 subscriptions', () => {
@withQueries({
subscriptions: {
items: (hz) => hz('items'),
users: (hz) => hz('users'),
}
})
class WithSubscriptions extends React.Component {
render() {
return <div/>
}
}

const wrapper = mount((
<HorizonProvider instance={Horizon()}>
<WithSubscriptions />
</HorizonProvider>
));

expect(wrapper.find(WithSubscriptions).nodes[0]._subscriptions)
.to.have.length(2);
});

it('should create container component with 2 mutations', () => {
@withQueries({
mutations: {
createItem: (hz) => (item) => hz('items').store(item),
addUser: (hz) => (user) => hz('users').store(user),
}
})
class WithMutations extends React.Component {
render() {
return <div/>
}
}

const wrapper = mount((
<HorizonProvider instance={Horizon()}>
<WithMutations />
</HorizonProvider>
));

expect(wrapper.find(WithMutations).nodes[0]._mutations)
.to.have.keys(['createItem', 'addUser']);
});
});