(https://twitter.com/dadc)",
39 | "license": "MIT",
40 | "bugs": {
41 | "url": "https://github.com/dacz/rxr-react/issues"
42 | },
43 | "homepage": "https://github.com/dacz/rxr-react",
44 | "ava": {
45 | "files": [
46 | "test/**/test-*.js"
47 | ],
48 | "require": [
49 | "babel-register"
50 | ],
51 | "babel": "inherit"
52 | },
53 | "devDependencies": {
54 | "ava": "^0.15.2",
55 | "babel": "^6.5.2",
56 | "babel-cli": "^6.10.1",
57 | "babel-core": "^6.10.4",
58 | "babel-eslint": "^6.1.2",
59 | "babel-loader": "^6.2.4",
60 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
61 | "babel-plugin-transform-runtime": "^6.9.0",
62 | "babel-preset-es2015": "^6.9.0",
63 | "babel-preset-react": "^6.11.1",
64 | "babel-preset-stage-0": "^6.5.0",
65 | "babel-register": "^6.9.0",
66 | "cross-env": "^2.0.0",
67 | "enzyme": "^2.4.1",
68 | "es3ify": "^0.2.2",
69 | "eslint": "^3.1.1",
70 | "eslint-config-dacz": "^0.2.0",
71 | "eslint-plugin-import": "^1.11.0",
72 | "eslint-plugin-jsx-a11y": "^2.0.1",
73 | "eslint-plugin-react": "^5.2.2",
74 | "jsdom": "^9.4.1",
75 | "react": "^15.2.1",
76 | "react-addons-test-utils": "^15.2.1",
77 | "react-dom": "^15.2.1",
78 | "rimraf": "^2.5.3",
79 | "webpack": "^1.13.1"
80 | },
81 | "dependencies": {
82 | "is-observable": "^0.2.0",
83 | "rxjs": "^5.0.0-beta.10",
84 | "rxjs-es": "^5.0.0-beta.10",
85 | "symbol-observable": "^1.0.1"
86 | },
87 | "peerDependencies": {
88 | "react": "^15.2.1"
89 | },
90 | "npmName": "rxr-react",
91 | "npmFileMap": [
92 | {
93 | "basePath": "/dist/",
94 | "files": [
95 | "*.js"
96 | ]
97 | }
98 | ]
99 | }
100 |
--------------------------------------------------------------------------------
/prepublish.js:
--------------------------------------------------------------------------------
1 | var glob = require('glob')
2 | var fs = require('fs')
3 | var es3ify = require('es3ify')
4 |
5 | glob('./@(lib|dist)/**/*.js', function (err, files) {
6 | if (err) {
7 | throw err
8 | }
9 |
10 | files.forEach(function (file) {
11 | fs.readFile(file, 'utf8', function (err, data) {
12 | if (err) {
13 | throw err
14 | }
15 |
16 | fs.writeFile(file, es3ify.transform(data), function (err) {
17 | if (err) {
18 | throw err
19 | }
20 |
21 | console.log('es3ified ' + file) // eslint-disable-line no-console
22 | })
23 | })
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/src/components/Provider.js:
--------------------------------------------------------------------------------
1 | import { Component, PropTypes, Children } from 'react';
2 |
3 | export default class Provider extends Component {
4 |
5 | static propTypes = {
6 | state$: PropTypes.object.isRequired,
7 | children: PropTypes.element.isRequired,
8 | };
9 |
10 | static childContextTypes = {
11 | state$: PropTypes.object.isRequired,
12 | }
13 |
14 | constructor(props, context) {
15 | super(props, context);
16 | this.state$ = props.state$;
17 | }
18 |
19 | getChildContext() {
20 | return { state$: this.state$ };
21 | }
22 |
23 | render() {
24 | return Children.only(this.props.children);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/connectWithState.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import deepEqual from 'deep-equal';
3 |
4 | const connectWithState = (selector = (state) => state) => (WrappedComponent) =>
5 | class ConnectWithState extends React.Component {
6 |
7 | static contextTypes = {
8 | state$: PropTypes.object,
9 | };
10 |
11 | constructor(props, context) {
12 | super(props, context);
13 | this.state$ = context.state$;
14 | }
15 |
16 | componentWillMount() {
17 | this.subscription = this.state$
18 | .map(selector)
19 | .distinctUntilChanged((a, b) => deepEqual(a, b))
20 | .subscribe(::this.setState);
21 | }
22 |
23 | componentWillUnmount() {
24 | this.subscription.unsubscribe();
25 | }
26 |
27 | render() {
28 | return (
29 |
30 | );
31 | }
32 | };
33 |
34 | export default connectWithState;
35 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Provider from './components/Provider';
2 | import connectWithState from './components/connectWithState';
3 |
4 | export { Provider, connectWithState };
5 |
--------------------------------------------------------------------------------
/test/test-Provider.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import React, { PropTypes } from 'react';
3 | import { mount } from 'enzyme';
4 | import Provider from '../src/components/Provider';
5 |
6 | import jsdom from 'jsdom';
7 | const doc = jsdom.jsdom('');
8 | global.document = doc;
9 | global.window = doc.defaultView;
10 |
11 | const MyComponent = (props, context) => { context.state$.some }
;
12 | MyComponent.contextTypes = {
13 | state$: PropTypes.object,
14 | };
15 |
16 | test('provides state$ as a context', t => {
17 | const state = { some: 'value' };
18 | const WrappedComponent = ;
19 | const renderedComponent = mount(WrappedComponent);
20 | t.is(renderedComponent.text(), state.some);
21 | });
22 |
--------------------------------------------------------------------------------
/test/test-connectWithState.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import React from 'react';
3 | import Rx from 'rxjs';
4 | import { mount } from 'enzyme';
5 | import connectWithState from '../src/components/connectWithState';
6 | import Provider from '../src/components/Provider';
7 |
8 | import jsdom from 'jsdom';
9 | const doc = jsdom.jsdom('');
10 | global.document = doc;
11 | global.window = doc.defaultView;
12 |
13 | const MyComponent = (props) => { JSON.stringify(props) }
;
14 |
15 |
16 | test('connects with state', t => {
17 | const state$ = new Rx.ReplaySubject(1);
18 |
19 | // initial state
20 | state$.next({});
21 | const WrappedComponent = connectWithState()(MyComponent);
22 | const renderedComponent = mount();
23 |
24 | let text = JSON.parse(renderedComponent.find('.myDiv').text());
25 | t.deepEqual(text, {});
26 |
27 | // value
28 | const nextState = { value: 'myValue' };
29 | state$.next(nextState);
30 | text = JSON.parse(renderedComponent.find('.myDiv').text());
31 | t.deepEqual(text, nextState);
32 | });
33 |
34 |
35 | test('connects with state with selector', t => {
36 | const state$ = new Rx.ReplaySubject(1);
37 |
38 | // initial state
39 | state$.next({});
40 | const selector = (state) => ({
41 | wantProp: state.fromProp ? state.fromProp * 2 : 0,
42 | });
43 | const WrappedComponent = connectWithState(selector)(MyComponent);
44 | const renderedComponent = mount();
45 |
46 | // empty
47 | let text = JSON.parse(renderedComponent.find('.myDiv').text());
48 | t.deepEqual(text, { wantProp: 0 }, 'empty');
49 |
50 | // not wanted state prop
51 | let nextState = { value: 'myValue' };
52 | state$.next(nextState);
53 | text = JSON.parse(renderedComponent.find('.myDiv').text());
54 | t.deepEqual(text, { wantProp: 0 }, 'not wanted state prop');
55 |
56 | // wanted state prop (transformed)
57 | nextState = { fromProp: 5 };
58 | state$.next(nextState);
59 | text = JSON.parse(renderedComponent.find('.myDiv').text());
60 | t.deepEqual(text, { wantProp: 10 }, 'wanted and transformed');
61 | });
62 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var webpack = require('webpack')
4 | var env = process.env.NODE_ENV
5 |
6 | var reactExternal = {
7 | root: 'React',
8 | commonjs2: 'react',
9 | commonjs: 'react',
10 | amd: 'react'
11 | }
12 |
13 | var rxrExternal = {
14 | root: 'rxr',
15 | commonjs2: 'rxr',
16 | commonjs: 'rxr',
17 | amd: 'rxr'
18 | }
19 |
20 | var config = {
21 | externals: {
22 | 'react': reactExternal,
23 | 'rxr': rxrExternal
24 | },
25 | module: {
26 | loaders: [
27 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ }
28 | ]
29 | },
30 | output: {
31 | library: 'ReactReactive',
32 | libraryTarget: 'umd'
33 | },
34 | plugins: [
35 | {
36 | apply: function apply(compiler) {
37 | compiler.parser.plugin('expression global', function expressionGlobalPlugin() {
38 | this.state.module.addVariable('global', "(function() { return this; }()) || Function('return this')()")
39 | return false
40 | })
41 | }
42 | },
43 | new webpack.optimize.OccurenceOrderPlugin(),
44 | new webpack.DefinePlugin({
45 | 'process.env.NODE_ENV': JSON.stringify(env)
46 | })
47 | ]
48 | }
49 |
50 | if (env === 'production') {
51 | config.plugins.push(
52 | new webpack.optimize.UglifyJsPlugin({
53 | compressor: {
54 | pure_getters: true,
55 | unsafe: true,
56 | unsafe_comps: true,
57 | screw_ie8: true,
58 | warnings: false
59 | }
60 | })
61 | )
62 | }
63 |
64 | module.exports = config
65 |
--------------------------------------------------------------------------------