├── .eslintrc
├── .gitignore
├── .meteor
├── .finished-upgraders
├── .gitignore
├── .id
├── packages
├── platforms
├── release
└── versions
├── README.md
├── client
├── actions
│ └── actions.js
├── components
│ ├── Errors
│ │ └── Errors.jsx
│ ├── MessagesEditor
│ │ └── MessagesEditor.jsx
│ └── MessagesList
│ │ └── MessagesList.jsx
├── containers
│ └── App.jsx
├── main.html
├── main.js
├── reducers
│ └── reducers.js
└── store
│ └── createStore.js
├── lib
└── messages.jsx
├── package.json
└── server
├── main.js
├── methods.js
└── publications.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "parser": "babel-eslint",
4 | "rules": {
5 | "id-length": [2, {
6 | "exceptions": ["_"]
7 | }],
8 | "arrow-body-style": 0,
9 | "camelcase": 0,
10 | "consistent-return": 0,
11 | "curly": 0,
12 | "func-names": 0,
13 | "new-cap": 0,
14 | "no-param-reassign": 0,
15 | "prefer-arrow-callback": 0,
16 | "space-before-function-paren": 0,
17 | "react/no-multi-comp": [2, {
18 | "ignoreStateless": true
19 | }],
20 | "react/prefer-stateless-function": 0,
21 | "react/jsx-no-bind": 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .tern-port
3 |
--------------------------------------------------------------------------------
/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | vq6rq61988jma1l59dr2
8 |
--------------------------------------------------------------------------------
/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-base # Packages every Meteor app needs to have
8 | mobile-experience # Packages for a great mobile UX
9 | mongo # The database Meteor supports right now
10 | blaze-html-templates # Compile .html files into Meteor Blaze views
11 | reactive-var # Reactive variable for tracker
12 | jquery # Helpful client-side library
13 | tracker # Meteor's client-side reactive programming library
14 |
15 | standard-minifier-css # CSS minifier run for production mode
16 | standard-minifier-js # JS minifier run for production mode
17 | es5-shim # ECMAScript 5 compatibility for older browsers.
18 | ecmascript # Enable ECMAScript2015+ syntax in app code
19 |
20 | simple:dev-error-overlay
21 |
--------------------------------------------------------------------------------
/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.1
2 |
--------------------------------------------------------------------------------
/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.0.3
2 | autoupdate@1.2.7
3 | babel-compiler@6.6.1
4 | babel-runtime@0.1.7
5 | base64@1.0.7
6 | binary-heap@1.0.7
7 | blaze@2.1.6
8 | blaze-html-templates@1.0.3
9 | blaze-tools@1.0.7
10 | boilerplate-generator@1.0.7
11 | caching-compiler@1.0.3
12 | caching-html-compiler@1.0.5
13 | callback-hook@1.0.7
14 | check@1.1.3
15 | ddp@1.2.4
16 | ddp-client@1.2.4
17 | ddp-common@1.2.4
18 | ddp-server@1.2.5
19 | deps@1.0.11
20 | diff-sequence@1.0.4
21 | ecmascript@0.4.2
22 | ecmascript-runtime@0.2.9
23 | ejson@1.0.10
24 | es5-shim@4.5.9
25 | fastclick@1.0.10
26 | geojson-utils@1.0.7
27 | hot-code-push@1.0.3
28 | html-tools@1.0.8
29 | htmljs@1.0.8
30 | http@1.1.4
31 | id-map@1.0.6
32 | jquery@1.11.7
33 | launch-screen@1.0.10
34 | less@2.5.7
35 | livedata@1.0.17
36 | logging@1.0.11
37 | meteor@1.1.13
38 | meteor-base@1.0.3
39 | minifier-css@1.1.10
40 | minifier-js@1.1.10
41 | minimongo@1.0.13
42 | mobile-experience@1.0.3
43 | mobile-status-bar@1.0.11
44 | modules@0.5.2
45 | modules-runtime@0.6.2
46 | mongo@1.1.6
47 | mongo-id@1.0.3
48 | npm-mongo@1.4.42
49 | observe-sequence@1.0.10
50 | ordered-dict@1.0.6
51 | promise@0.6.6
52 | random@1.0.8
53 | reactive-var@1.0.8
54 | reload@1.1.7
55 | retry@1.0.6
56 | routepolicy@1.0.9
57 | simple:dev-error-overlay@1.5.1
58 | spacebars@1.0.10
59 | spacebars-compiler@1.0.10
60 | standard-minifier-css@1.0.5
61 | standard-minifier-js@1.0.5
62 | templating@1.1.8
63 | templating-tools@1.0.3
64 | tracker@1.0.12
65 | ui@1.0.10
66 | underscore@1.0.7
67 | url@1.0.8
68 | webapp@1.2.7
69 | webapp-hashing@1.0.8
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Meteor Redux Demo
2 | =================
3 |
4 | This repository contains a demonstration application for the article [A bridge
5 | between React and
6 | Meteor](https://subvisual.co/blog/posts/79-working-with-meteor-react-and-redux).
7 | In this application you can write, remove, and see messages. If you try to write an empty
8 | message you'll see an error. While the messages are loading you see a
9 | "loading" message.
10 |
11 | The purpose of this application is to demonstrate a possible setup for Redux on
12 | Meteor, most code written here is not acceptable on production application.
13 |
14 | This application uses the package
15 | [meteor-ditto](https://github.com/gabrielpoca/meteor-ditto). It's a working in
16 | progress implementing the system I talk about in the blog post.
17 |
--------------------------------------------------------------------------------
/client/actions/actions.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 |
3 | export const createMessage = params => {
4 | return dispatch => {
5 | Meteor.call('createMessage', params, (error) => {
6 | if (!error) return;
7 |
8 | dispatch({
9 | type: 'ADD_ERROR',
10 | error,
11 | });
12 | });
13 | };
14 | };
15 |
16 | export const removeMessage = id => {
17 | return () => {
18 | Meteor.call('removeMessage', id);
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/client/components/Errors/Errors.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | class ErrorsList extends Component {
4 | renderError(error, index) {
5 | return
{error.message};
6 | }
7 |
8 | render() {
9 | return {this.props.errors.map(this.renderError)}
;
10 | }
11 | }
12 |
13 | ErrorsList.propTypes = {
14 | errors: PropTypes.array.isRequired,
15 | };
16 |
17 | export default ErrorsList;
18 |
--------------------------------------------------------------------------------
/client/components/MessagesEditor/MessagesEditor.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { reduxForm } from 'redux-form';
3 |
4 | const fields = ['text'];
5 |
6 | class MessagesEditor extends Component {
7 | render() {
8 | const { fields: { text }, handleSubmit } = this.props;
9 |
10 | return (
11 |
15 | );
16 | }
17 | }
18 |
19 | MessagesEditor.propTypes = {
20 | fields: PropTypes.object.isRequired,
21 | handleSubmit: PropTypes.func.isRequired,
22 | };
23 |
24 | export default reduxForm({
25 | fields,
26 | form: 'new-message',
27 | })(MessagesEditor);
28 |
--------------------------------------------------------------------------------
/client/components/MessagesList/MessagesList.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | class MessagesList extends Component {
4 | renderMessage({ _id, text }, index) {
5 | return (
6 |
7 | {text} this.props.onRemove(_id)}>Remove
8 |
9 | );
10 | }
11 |
12 | renderLoading() {
13 | return Loading messages...
;
14 | }
15 |
16 | render() {
17 | if (!this.props.ready)
18 | return this.renderLoading();
19 | return {this.props.messages.map(this.renderMessage.bind(this))}
;
20 | }
21 | }
22 |
23 | MessagesList.propTypes = {
24 | messages: PropTypes.array.isRequired,
25 | onRemove: PropTypes.func.isRequired,
26 | ready: PropTypes.bool,
27 | };
28 |
29 | export default MessagesList;
30 |
--------------------------------------------------------------------------------
/client/containers/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 | import { createMessage, removeMessage } from '../actions/actions';
5 | import { reset } from 'redux-form';
6 |
7 | import { SubscriptionComponent } from 'meteor-ditto';
8 | import MessagesList from '../components/MessagesList/MessagesList';
9 | import MessagesEditor from '../components/MessagesEditor/MessagesEditor';
10 | import Errors from '../components/Errors/Errors';
11 |
12 | class App extends Component {
13 | componentWillMount() {
14 | this.props.subscribe('messages');
15 | }
16 |
17 | handleSubmit(fields) {
18 | this.props.createMessage(fields);
19 | this.props.reset('new-message');
20 | }
21 |
22 | handleRemove(id) {
23 | this.props.removeMessage(id);
24 | }
25 |
26 | render() {
27 | return (
28 |
29 |
34 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | App.propTypes = {
44 | subscribe: PropTypes.func.isRequired,
45 | subscriptionReady: PropTypes.func.isRequired,
46 | createMessage: PropTypes.func.isRequired,
47 | removeMessage: PropTypes.func.isRequired,
48 | reset: PropTypes.func.isRequired,
49 | messages: PropTypes.array.isRequired,
50 | errors: PropTypes.array.isRequired,
51 | };
52 |
53 | const mapStateToProps = state => {
54 | return {
55 | messages: state.mongo.collections.messages || [],
56 | errors: state.errors,
57 | };
58 | };
59 |
60 | const mapDispatchToProps = dispatch => {
61 | return bindActionCreators({ createMessage, removeMessage, reset }, dispatch);
62 | };
63 |
64 | export default connect(mapStateToProps, mapDispatchToProps)(SubscriptionComponent(App));
65 |
--------------------------------------------------------------------------------
/client/main.html:
--------------------------------------------------------------------------------
1 |
2 | Meteor Redux Demo
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/client/main.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import React from 'react';
3 | import { render } from 'react-dom';
4 | import { Provider } from 'react-redux';
5 | import { Router, Route, browserHistory } from 'react-router';
6 |
7 | import createStore from './store/createStore';
8 | import App from './containers/App';
9 |
10 | Meteor.startup(() => {
11 | render((
12 |
13 |
14 |
15 |
16 |
17 | ), document.getElementById('app'));
18 | });
19 |
--------------------------------------------------------------------------------
/client/reducers/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { reducer as formReducer } from 'redux-form';
3 | import { mongo } from 'meteor-ditto';
4 |
5 | const errorsReducer = (state = [], action) => {
6 | switch (action.type) {
7 | case 'ADD_ERROR':
8 | return [...state, action.error];
9 | default:
10 | return state;
11 | }
12 | };
13 |
14 | export default combineReducers({
15 | errors: errorsReducer,
16 | form: formReducer,
17 | mongo,
18 | });
19 |
--------------------------------------------------------------------------------
/client/store/createStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 |
4 | import { connect as connectCollection } from 'meteor-ditto';
5 | import reducers from '../reducers/reducers';
6 | import Messages from '../../lib/messages';
7 |
8 | export default () => {
9 | const store = createStore(reducers, compose(
10 | applyMiddleware(thunk),
11 | window.devToolsExtension ? window.devToolsExtension() : fn => fn,
12 | ));
13 |
14 | connectCollection(Messages, store);
15 |
16 | return store;
17 | };
18 |
--------------------------------------------------------------------------------
/lib/messages.jsx:
--------------------------------------------------------------------------------
1 | import { Mongo } from 'meteor/mongo';
2 |
3 | Messages = new Mongo.Collection('messages');
4 |
5 | export default Messages;
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "meteor-redux-demo",
3 | "private": true,
4 | "scripts": {
5 | "start": "meteor run"
6 | },
7 | "dependencies": {
8 | "lodash": "^4.11.1",
9 | "meteor-ditto": "0.0.4",
10 | "meteor-node-stubs": "~0.2.0",
11 | "react": "^15.0.1",
12 | "react-dom": "^15.0.1",
13 | "react-redux": "^4.4.5",
14 | "react-router": "^2.2.2",
15 | "redux": "^3.4.0",
16 | "redux-form": "^5.0.1",
17 | "redux-thunk": "^2.0.1"
18 | },
19 | "devDependencies": {
20 | "babel-eslint": "^6.0.2",
21 | "eslint": "^2.7.0",
22 | "eslint-config-airbnb": "^6.2.0",
23 | "eslint-config-shakacode": "^4.0.0",
24 | "eslint-plugin-react": "^4.2.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/main.js:
--------------------------------------------------------------------------------
1 | import './publications';
2 | import './methods';
3 |
--------------------------------------------------------------------------------
/server/methods.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import Messages from '../lib/messages';
3 |
4 | const createMessage = function(params) {
5 | if (!params.text)
6 | throw new Meteor.Error('text missing', 'Cannot submit an empty message');
7 |
8 | Messages.insert(params);
9 | };
10 |
11 | const removeMessage = function(_id) {
12 | if (!_id)
13 | throw new Meteor.Error('id missing', 'An id is needed to remove a message');
14 |
15 | Messages.remove({ _id });
16 | };
17 |
18 | Meteor.methods({
19 | createMessage,
20 | removeMessage,
21 | });
22 |
--------------------------------------------------------------------------------
/server/publications.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import Messages from '../lib/messages';
3 |
4 | Meteor.publish('messages', function() {
5 | return Messages.find({});
6 | });
7 |
--------------------------------------------------------------------------------