├── .babelrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── example
├── .hz
│ └── config.toml
├── dist
│ ├── index.html
│ └── js
│ │ └── bundle.js
└── src
│ ├── components
│ ├── chat.js
│ ├── input.js
│ ├── list.js
│ └── message.js
│ └── index.js
├── jsconfig.json
├── logo.png
├── package.json
├── schema.png
├── src
├── connect.js
├── index.js
├── provider.js
└── route.js
└── test
├── .setup.js
├── connect.spec.js
├── provider.spec.js
├── route.spec.js
├── utils.js
└── withQueries.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["airbnb", "stage-1"],
3 | "plugins": [
4 | "transform-decorators-legacy"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | example/rethinkdb_data
3 | build
4 | *.tgz
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | example/
3 | test/
4 | *.png
5 | .npmignore
6 | .babelrc
7 | .travis.yml
8 | jsconfig.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "5"
5 | before_install:
6 | - npm install @horizon/client react
7 | before_script:
8 | - npm run build
9 | branches:
10 | only:
11 | - master
12 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Roman Liutikov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/react-hz)
2 | [](https://travis-ci.org/roman01la/react-horizon)
3 |
4 | # React Horizon
5 |
6 |
7 |
8 | *React Horizon makes it easier to use your React application with horizon.io realtime backend*
9 |
10 | ## Installation
11 | ```
12 | $ npm i react-hz
13 | ```
14 |
15 | React Horizon allows reactive dataflow between backend and React.js application. Client demand is declared in React components using Horizon's query API and data is synchronized thanks to [horizon.io](http://horizon.io/) realtime backend.
16 |
17 |
18 |
19 | ## Running example
20 | - Make sure you have installed RethinkDB and Horizon's CLI
21 | - Start server from `example` directory: `$ hz serve --dev`
22 | - Open http://127.0.0.1:8181 in your browser
23 |
24 | ## Usage
25 |
26 | Read Horizon's [Collection API](http://horizon.io/api/collection/) for querying methods.
27 |
28 | `react-hz` package provides `HorizonProvider` instance provider component, `HorizonRoute` application route component, connector function and `Horizon` client library.
29 |
30 | ### ``
31 | `HorizonProvider` is a top level component in your application which establishes connection to Horizon server. The component accepts an instance of `Horizon` constructor as `instance` prop.
32 | ```js
33 |
34 |
35 |
36 | ```
37 |
38 | ### `Horizon([config])`
39 | `Horizon` is a constructor function from Horizon's client library included into `react-hz`. Constructor function accepts optional config object http://horizon.io/api/horizon/#constructor.
40 | ```js
41 | const horizonInstance = Horizon({ host: 'localhost:8181' });
42 | ```
43 |
44 | ### ``
45 | `HorizonRoute` is a top level component for every screen in your application which provides an API to respond to connectivity status changes.
46 | Normally you should render your app in `renderSuccess` callback. `renderFailure` callback receives error object which can be used to render an error message.
47 | ```js
48 |
Connecting...
}
50 | renderDisconnected={() =>
You are offline
}
51 | renderConnected={() =>
You are online
}
52 | renderSuccess={() =>
Hello!
}
53 | renderFailure={(error) =>
Something went wrong...
} />
54 | ```
55 |
56 | ### `connect(component, config)`
57 | `connect` function wraps React components with specified queries for subscriptions and mutations. Connector function expects two arguments: React component and subscriptions/mutations config object. Props passed into container component are automatically passed into wrapped component.
58 | ```js
59 | const AppContainer = connect(App, {
60 | subscriptions: {
61 | // ...
62 | },
63 | mutations: {
64 | // ...
65 | }
66 | });
67 | ```
68 |
69 | ### `withQueries(config)`
70 | `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:
71 | ```js
72 |
73 | import {withQueries} from 'react-hz'
74 |
75 | @withQueries({
76 | subscriptions: {
77 | // ...
78 | },
79 | mutations: {
80 | // ...
81 | }
82 | })
83 | class MyComponent extends Component {
84 | // ...
85 | }
86 | ```
87 |
88 |
89 |
90 | ### Subscriptions
91 |
92 | `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.
93 |
94 | Behind the scenes React Horizon calls `watch` and `subscribe` function on query object which returns RxJS Observable and subscribes to incoming data. Data received by that observable is then passed into React component as props.
95 |
96 | All subscriptions are unsubscribed automatically on `componentWillUnmount`.
97 |
98 | ```js
99 | import React, { Component } from 'react';
100 | import { render } from 'react-dom';
101 | import { Horizon, HorizonProvider, connect } from 'react-hz';
102 |
103 | class App extends Component {
104 | render() {
105 |
106 | const itemsSubcription = this.props.items;
107 |
108 | return (
109 |
{itemsSubcription.map(({ id, title }) =>
{title}
)}
110 | );
111 | }
112 | }
113 |
114 | const AppContainer = connect(App, {
115 | subscriptions: {
116 | items: (hz, { username }) => hz('items')
117 | .find({ username })
118 | .below({ id: 10 })
119 | .order('title', 'ascending')
120 | }
121 | });
122 |
123 | render((
124 |
125 |
126 |
127 | ), document.getElementById('app'));
128 | ```
129 |
130 | ### Mutations
131 |
132 | `mutations` is a map of mutation query names to mutation query functions. Specified mutations are available as props in React component behind their corresponding names in config.
133 |
134 | Available mutation operations:
135 | - `remove` - http://horizon.io/api/collection/#remove
136 | - `removeAll` - http://horizon.io/api/collection/#removeall
137 | - `replace` - http://horizon.io/api/collection/#replace
138 | - `store` - http://horizon.io/api/collection/#store
139 | - `upsert` - http://horizon.io/api/collection/#upsert
140 |
141 | It's possible to create two types of mutations (see example below):
142 | - generic mutation which provides mutation object and thus gives you an ability to call different mutation operations in component
143 | - specific mutation which is a function that receives parameters required for mutation, instantiates mutations object and applies mutation immediately
144 |
145 | ```js
146 | import React, { Component } from 'react';
147 | import { render } from 'react-dom';
148 | import { Horizon, HorizonProvider, connect } from 'react-hz';
149 |
150 | class App extends Component {
151 | render() {
152 |
153 | const itemsMutation = this.props.items;
154 | const removeItem = this.props.removeItem;
155 |
156 | return (
157 |
158 |
159 |
160 |
161 | );
162 | }
163 | }
164 |
165 | const AppContainer = connectHorizon(App, {
166 | mutations: {
167 | items: (hz) => hz('items'),
168 | removeItem: (hz) => (id) => hz('items').remove(id)
169 | }
170 | });
171 |
172 | render((
173 |
174 |
175 |
176 | ), document.getElementById('app'));
177 | ```
178 |
179 | ## Limitations
180 |
181 | - **GraphQL**. GraphQL would be a much better declarative replacement instead of current Collection API. Horizon team is working on GraphQL adapter, follow [this thread](https://github.com/rethinkdb/horizon/issues/125) for updates.
182 | - **Optimistic updates**. Optimistic updates feature is [being discussed](https://github.com/rethinkdb/horizon/issues/23) and it seems like it's not obvious at the moment if this should be baked into client library.
183 | - **Offline**. [Offline support is not implemented yet](https://github.com/rethinkdb/horizon/issues/58).
184 | - **Managing reconnection**. [Automatic reconnection is not implemented yet](https://github.com/rethinkdb/horizon/issues/9).
185 |
186 | MIT
187 |
--------------------------------------------------------------------------------
/example/.hz/config.toml:
--------------------------------------------------------------------------------
1 | # This is a TOML file
2 |
3 | ###############################################################################
4 | # IP options
5 | # 'bind' controls which local interfaces will be listened on
6 | # 'port' controls which port will be listened on
7 | #------------------------------------------------------------------------------
8 | # bind = [ "localhost" ]
9 | # port = 8181
10 |
11 |
12 | ###############################################################################
13 | # HTTPS Options
14 | # 'secure' will disable HTTPS and use HTTP instead when set to 'false'
15 | # 'key_file' and 'cert_file' are required for serving HTTPS
16 | #------------------------------------------------------------------------------
17 | # secure = false
18 | # key_file = "horizon-key.pem"
19 | # cert_file = "horizon-cert.pem"
20 |
21 |
22 | ###############################################################################
23 | # App Options
24 | # 'project_name' sets the name of the RethinkDB database used to store the
25 | # application state
26 | # 'serve_static' will serve files from the given directory over HTTP/HTTPS
27 | #------------------------------------------------------------------------------
28 | project_name = "example"
29 | # serve_static = "dist"
30 |
31 |
32 | ###############################################################################
33 | # Data Options
34 | # WARNING: these should probably not be enabled on a publically accessible
35 | # service. Tables and indexes are not lightweight objects, and allowing them
36 | # to be created like this could open the service up to denial-of-service
37 | # attacks.
38 | # 'auto_create_collection' creates a collection when one is needed but does not exist
39 | # 'auto_create_index' creates an index when one is needed but does not exist
40 | #------------------------------------------------------------------------------
41 | # auto_create_collection = true
42 | # auto_create_index = true
43 |
44 |
45 | ###############################################################################
46 | # RethinkDB Options
47 | # These options are mutually exclusive
48 | # 'connect' will connect to an existing RethinkDB instance
49 | # 'start_rethinkdb' will run an internal RethinkDB instance
50 | #------------------------------------------------------------------------------
51 | # connect = "localhost:28015"
52 | # start_rethinkdb = false
53 |
54 |
55 | ###############################################################################
56 | # Debug Options
57 | # 'debug' enables debug log statements
58 | #------------------------------------------------------------------------------
59 | # debug = true
60 |
61 |
62 | ###############################################################################
63 | # Authentication Options
64 | # Each auth subsection will add an endpoint for authenticating through the
65 | # specified provider.
66 | # 'token_secret' is the key used to sign jwts
67 | # 'allow_anonymous' issues new accounts to users without an auth provider
68 | # 'allow_unauthenticated' allows connections that are not tied to a user id
69 | # 'auth_redirect' specifies where users will be redirected to after login
70 | #------------------------------------------------------------------------------
71 | token_secret = "homvHswmmQYjzZYUAkvFAu/E26CdP9sdoZGpthngJg8bN1C4aYtA2aZW/3I4iLyJGVgvjbrLT8bPRqDWm0D9+w=="
72 | # allow_anonymous = true
73 | # allow_unauthenticated = true
74 | # auth_redirect = "/"
75 | #
76 | # [auth.facebook]
77 | # id = "000000000000000"
78 | # secret = "00000000000000000000000000000000"
79 | #
80 | # [auth.google]
81 | # id = "00000000000-00000000000000000000000000000000.apps.googleusercontent.com"
82 | # secret = "000000000000000000000000"
83 | #
84 | # [auth.twitter]
85 | # id = "0000000000000000000000000"
86 | # secret = "00000000000000000000000000000000000000000000000000"
87 | #
88 | # [auth.github]
89 | # id = "00000000000000000000"
90 | # secret = "0000000000000000000000000000000000000000"
91 | #
92 | # [auth.twitch]
93 | # id = "0000000000000000000000000000000"
94 | # secret = "0000000000000000000000000000000"
95 |
--------------------------------------------------------------------------------
/example/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Horizon
6 |
7 |
8 |
58 |
59 |
60 |
61 |