├── .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 ; 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 |
    12 | 13 | 14 |
    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 ; 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 | --------------------------------------------------------------------------------