redux basic setup (to be improved) - test case on FlashMessages, used in PostNewForm

This commit is contained in:
xavcz 2016-10-19 10:32:24 +02:00
parent e88ecae84d
commit f6bbbd14ca
14 changed files with 145 additions and 32 deletions

View file

@ -6,6 +6,7 @@ aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.0
aldeed:simple-schema@1.5.3
allow-deny@1.0.5
apollo@0.1.2
autoupdate@1.3.11
babel-compiler@6.9.1
babel-runtime@0.1.11

View file

@ -10,21 +10,21 @@ class Flash extends Component{
}
componentDidMount() {
this.context.messages.markAsSeen(this.props.message._id);
this.props.markAsSeen(this.props.message._id);
}
dismissFlash(e) {
e.preventDefault();
this.context.messages.clear(this.props.message._id);
this.props.clear(this.props.message._id);
}
render() {
let type = this.props.message.type;
type = type === "error" ? "danger" : type; // if type is "error", use "danger" instead
let flashType = this.props.message.flashType;
flashType = flashType === "error" ? "danger" : flashType; // if flashType is "error", use "danger" instead
return (
<Alert className="flash-message" bsStyle={type} onDismiss={this.dismissFlash}>
<Alert className="flash-message" bsStyle={flashType} onDismiss={this.dismissFlash}>
{this.props.message.content}
</Alert>
)
@ -35,8 +35,4 @@ Flash.propTypes = {
message: React.PropTypes.object.isRequired
}
Flash.contextTypes = {
messages: React.PropTypes.object.isRequired
}
module.exports = Flash;

View file

@ -1,10 +1,12 @@
import Telescope from 'meteor/nova:lib';
import React from 'react';
const FlashMessages = ({messages}) => {
const FlashMessages = ({messages, clear, markAsSeen}) => {
return (
<div className="flash-messages">
{messages.map((message, index) => <Telescope.components.Flash key={index} message={message} />)}
{messages
.filter(message => message.show)
.map(message => <Telescope.components.Flash key={message._id} message={message} clear={clear} markAsSeen={markAsSeen} />)}
</div>
);
}

View file

@ -5,7 +5,6 @@ import { FlashContainer } from "meteor/nova:core";
class Layout extends Component {
render() {
return (
<div className="wrapper" id="wrapper">
@ -17,7 +16,7 @@ class Layout extends Component {
<div className="main">
<FlashContainer component={Telescope.components.FlashMessages}/>
<FlashContainer {...this.props} component={Telescope.components.FlashMessages}/>
<Telescope.components.Newsletter />
@ -29,7 +28,6 @@ class Layout extends Component {
</div>
)
}
}

View file

@ -1,5 +1,7 @@
import Telescope from 'meteor/nova:lib';
import React, { PropTypes, Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { intlShape } from 'react-intl';
import NovaForm from "meteor/nova:forms";
import { withRouter } from 'react-router'
@ -20,7 +22,7 @@ const PostsNewForm = (props, context) => {
collection={Posts}
methodName="posts.new"
successCallback={(post)=>{
context.messages.flash(context.intl.formatMessage({id: "posts.created_message"}), "success");
props.flash(context.intl.formatMessage({id: "posts.created_message"}), "success");
router.push({pathname: Posts.getPageUrl(post)});
}}
/>
@ -31,11 +33,14 @@ const PostsNewForm = (props, context) => {
PostsNewForm.contextTypes = {
currentUser: React.PropTypes.object,
messages: React.PropTypes.object,
intl: intlShape
};
PostsNewForm.displayName = "PostsNewForm";
module.exports = withRouter(PostsNewForm);
export default withRouter(PostsNewForm);
const mapStateToProps = state => ({ messages: state.messages });
const mapDispatchToProps = dispatch => bindActionCreators(Telescope.actions.messages, dispatch);
// note: why having both module.exports & export default?
module.exports = withRouter(connect(mapStateToProps, mapDispatchToProps)(PostsNewForm));
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PostsNewForm));

View file

@ -12,6 +12,10 @@ import Helmet from 'react-helmet';
import Cookie from 'react-cookie';
import ReactDOM from 'react-dom';
// redux
import { Provider } from 'react-redux';
import store from "./store.js";
Telescope.routes.indexRoute = { name: "posts.list", component: Telescope.components.PostsHome };
Meteor.startup(() => {
@ -24,13 +28,19 @@ Meteor.startup(() => {
{name:"users.edit", path:"users/:slug/edit", component:Telescope.components.UsersAccount},
{name:"app.notfound", path:"*", component:Telescope.components.Error404},
]);
const ProvidedApp = (props) => (
<Provider store={store}>
<Telescope.components.App {...props} />
</Provider>
);
const AppRoutes = {
path: '/',
component: Telescope.components.App,
component: ProvidedApp,
indexRoute: Telescope.routes.indexRoute,
childRoutes: Telescope.routes.routes
}
};
let history;

View file

@ -0,0 +1,16 @@
import { createStore, compose, combineReducers, } from 'redux';
import Telescope from 'meteor/nova:lib';
const rootReducer = combineReducers(Telescope.reducers);
const defaultState = {
messages: [],
};
const enhancers = compose(
typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
);
const store = createStore(rootReducer, defaultState);
export default store;

View file

@ -24,6 +24,7 @@ Package.onUse(function (api) {
]);
api.addFiles([
'lib/store.js',
'lib/routes.jsx'
], ['client', 'server']);

View file

@ -3,8 +3,8 @@ import './modules.js';
import Messages from './messages.js';
import ModalTrigger from './components/ModalTrigger.jsx';
import ContextPasser from './components/ContextPasser.jsx';
import FlashContainer from './containers/FlashContainer.jsx';
import FlashContainer from "./containers/FlashContainer.jsx";
import AppComposer from './containers/AppComposer.jsx';
// import NovaCounts from './counts.js';
export { Messages, ModalTrigger, ContextPasser, AppComposer, FlashContainer};
export { Messages, ModalTrigger, ContextPasser, AppComposer, FlashContainer };

View file

@ -1,15 +1,64 @@
import React from 'react';
import Messages from "../messages.js";
import Telescope from 'meteor/nova:lib';
//import { messagesActions } from "../messages.js";
import { createContainer } from 'meteor/react-meteor-data';
// import { createContainer } from 'meteor/react-meteor-data';
const FlashContainer = createContainer(() => {
return {
messages: Messages.collection.find({show: true}).fetch()
// const FlashContainer = createContainer(() => {
// return {
// messages: Messages.collection.find({show: true}).fetch()
// }
// }, params => <params.component {...params} />);
// FlashContainer.displayName = "FlashContainer";
// module.exports = FlashContainer;
// export default FlashContainer;
// note: messy redux test
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// should go elsewhere...
Telescope.reducers.messages = (state = [], action) => {
let currentMsg = {};
switch(action.type) {
case 'FLASH':
action.flashType = typeof action.flashType === 'undefined' ? 'error' : action.flashType;
return [
...state,
{ _id: state.length, content: action.content, flashType: action.flashType, seen: false, show: true },
];
case 'MARK_AS_SEEN':
currentMsg = state[action.i];
return [
...state.slice(0, action.i),
{ ...currentMsg, seen: true },
...state.slice(action.i + 1),
];
case 'CLEAR':
currentMsg = state[action.i];
return [
...state.slice(0, action.i),
{ ...currentMsg, show: false },
...state.slice(action.i + 1),
];
case 'CLEAR_SEEN':
return state.map(message => ({ ...message, show: false }));
default:
return state;
}
}, params => <params.component {...params} />);
};
FlashContainer.displayName = "FlashContainer";
const mapStateToProps = state => ({
messages: state.messages,
});
const mapDispatchToProps = dispatch => bindActionCreators(Telescope.actions.messages, dispatch);
const FlashContainer = connect(mapStateToProps, mapDispatchToProps)((props, context) => <props.component {...props} />);
module.exports = FlashContainer;
export default FlashContainer;

View file

@ -1,3 +1,5 @@
import Telescope from 'meteor/nova:lib';
const Messages = {
// Local (client-only) collection
collection: new Meteor.Collection(null),
@ -21,4 +23,32 @@ const Messages = {
}
};
// actions
Telescope.actions.messages = {
flash(content, flashType) {
return {
type: 'FLASH',
content,
flashType,
};
},
clear(i) {
return {
type: 'CLEAR',
i,
};
},
markAsSeen(i) {
return {
type: 'MARK_AS_SEEN',
i,
};
},
clearSeen() {
return {
type: 'CLEAR_SEEN'
};
},
};
export default Messages;

View file

@ -1,2 +1,3 @@
import './callbacks.js';
import './icons.js';
import './icons.js';
import './messages.js'; // import redux actions

View file

@ -8,4 +8,4 @@ import ContextPasser from "./components/ContextPasser.jsx";
import FlashContainer from "./containers/FlashContainer.jsx";
import AppComposer from "./containers/AppComposer.jsx";
export { Messages, ModalTrigger, ContextPasser, AppComposer, FlashContainer};
export { Messages, ModalTrigger, ContextPasser, AppComposer, FlashContainer };

View file

@ -106,4 +106,8 @@ Telescope.statuses = [
}
];
// ---------------------------------- Redux ------------------------------------ //
Telescope.actions = {};
Telescope.reducers = {};
export default Telescope;