diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 58d7b84..a966925 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -2,39 +2,27 @@
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -48,22 +36,14 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -90,6 +70,7 @@
index
fetchLikes
Link
+ id
@@ -98,53 +79,57 @@
@@ -164,10 +149,10 @@
-
+
-
+
@@ -185,7 +170,6 @@
-
@@ -196,12 +180,34 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -395,41 +401,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
-
+
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
@@ -438,17 +449,17 @@
-
+
+
-
-
+
-
+
@@ -459,125 +470,120 @@
-
+
-
+
-
-
+
+
-
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
-
-
+
+
-
+
-
-
-
+
+
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
@@ -588,161 +594,260 @@
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
+
+
+
+
+
+
+
-
+
-
+
-
-
+
+
+
-
+
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index f111639..37bccf9 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,12 @@
# React-Blog
My project version on React for Webdevelopers Thinknetica course.
+
+## Lesson 6: Redux part 1
+
+Use redux, react-redux, redux-thunk & redux-devtools to create a store for client app.
+
+
+Tasks:
+1. Add actions (& asynchronous action creators fetchPosts/fetchPost), reducers & store for managing posts page & inner
+post page state.
\ No newline at end of file
diff --git a/index.html b/index.html
index 4bccbed..fbe5799 100644
--- a/index.html
+++ b/index.html
@@ -9,6 +9,7 @@
+
diff --git a/package.json b/package.json
index 5ee2d50..70ec81c 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "My project version on React for Webdevelopers Thinknetica course.",
"main": "index.js",
"scripts": {
- "start": "gulp build ./semantic/gulpfile.js",
+ "start": "node ./initializers/server/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
@@ -30,6 +30,9 @@
"eslint-plugin-react": "^7.1.0",
"file-loader": "^0.11.2",
"react-hot-loader": "^3.0.0-beta.6",
+ "redux-devtools": "^3.4.0",
+ "redux-devtools-dock-monitor": "^1.1.2",
+ "redux-devtools-log-monitor": "^1.3.0",
"semantic-ui": "^2.2.10",
"style-loader": "^0.18.2",
"url-loader": "^0.5.9",
@@ -42,9 +45,13 @@
"immutability-helper": "^2.2.3",
"lodash": "^4.17.4",
"moment": "^2.18.1",
+ "qs": "^6.5.1",
"react": "^15.6.1",
"react-dom": "^15.6.1",
+ "react-redux": "^5.0.6",
"react-router": "^3.0.2",
+ "redux": "^3.7.2",
+ "redux-thunk": "^2.2.0",
"semantic-ui-react": "^0.70.0",
"superagent": "^3.5.2"
}
diff --git a/src/App.js b/src/App.js
index 391e865..978599f 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,10 +1,36 @@
import React from 'react';
-
-import {Router, browserHistory} from 'react-router';
+import {Router, browserHistory, match} from 'react-router';
import routes from 'routes';
+import {Provider} from 'react-redux';
+import store from 'store';
+import prepareData from 'helpers/prepareData';
+import ReactDOM from 'react-dom';
+import DevTools from 'containers/DevTools';
+
+const historyCb = (location) => {
+ match(
+ {location, routes},
+ (err, redirect, state) => {
+ if (!err && !redirect) {
+ prepareData(store, state);
+ }
+ }
+ );
+};
+
+browserHistory.listenBefore(historyCb);
+
+historyCb(window.location);
const App = () => (
-
+
+
+
+);
+
+ReactDOM.render(
+ ,
+ document.getElementById('devtools')
);
export default App;
diff --git a/src/actions/Post.js b/src/actions/Post.js
new file mode 100644
index 0000000..e7c75a1
--- /dev/null
+++ b/src/actions/Post.js
@@ -0,0 +1,30 @@
+import * as types from '../constants/actionTypes/PostActionTypes';
+import request from 'superagent';
+import {API_ROOT} from '../constants/API';
+
+//fetching post
+const requestPost = () => ({
+ type: types.FETCH_POST_REQUEST
+});
+
+const errorPost = () => ({
+ type: types.FETCH_POST_ERROR
+});
+
+const receivePost = (response) => ({
+ type: types.FETCH_POST_SUCCESS,
+ response
+});
+
+export const fetchPost = (id) => (
+ (dispatch) => {
+ dispatch(requestPost(id));
+
+ return request
+ .get(`${API_ROOT}/posts/${id}`)
+ .end((err, response) => {
+ err ? dispatch(errorPost(id)) :
+ dispatch(receivePost(response.body));
+ });
+ }
+);
diff --git a/src/actions/Posts.js b/src/actions/Posts.js
new file mode 100644
index 0000000..b25e817
--- /dev/null
+++ b/src/actions/Posts.js
@@ -0,0 +1,30 @@
+import * as types from '../constants/actionTypes/PostsActionTypes';
+import request from 'superagent';
+import {API_ROOT} from '../constants/API';
+
+//fetching posts
+const requestPosts = () => ({
+ type: types.FETCH_POSTS_REQUEST
+});
+
+const errorPosts = () => ({
+ type: types.FETCH_POSTS_ERROR
+});
+
+const receivePosts = (response) => ({
+ type: types.FETCH_POSTS_SUCCESS,
+ response
+});
+
+export const fetchPosts = () => (
+ (dispatch) => {
+ dispatch(requestPosts());
+
+ return request
+ .get(`${API_ROOT}/posts`)
+ .end((err, response) => {
+ err ? dispatch(errorPosts()) :
+ dispatch(receivePosts(response.body));
+ });
+ }
+);
diff --git a/src/actions/actionCreators.js b/src/actions/actionCreators.js
new file mode 100644
index 0000000..55a7231
--- /dev/null
+++ b/src/actions/actionCreators.js
@@ -0,0 +1,19 @@
+import {REQUEST_INCREMENT_LIKE} from '../constants/actionTypes/actionTypes';
+import request from 'superagent';
+import {API_ROOT} from '../constants/API';
+
+//INCREMENT LIKE
+const requestIncrementLike = (id) => ({
+ type: REQUEST_INCREMENT_LIKE,
+ id
+});
+
+export const incrementLike = (id) => (
+ (dispatch) => {
+ return request
+ .put(`${API_ROOT}/posts/${id}`)
+ .end((err) => {
+ if (!err) dispatch(requestIncrementLike(id));
+ });
+ }
+);
diff --git a/src/components/BlogPage.js b/src/components/BlogPage.js
index 1dd7804..f35ec32 100644
--- a/src/components/BlogPage.js
+++ b/src/components/BlogPage.js
@@ -1,54 +1,12 @@
import React from 'react';
-
-import _ from 'lodash';
-
-import {updateItemsMetaInfo as update} from 'helpers/like';
-
-import BlogList from 'components/widgets/blog/BlogList';
-import PieChart from 'components/widgets/blog/PieChart';
-
-import {fetchPosts} from 'helpers/rest';
-import {fetchMetaInfo} from '../helpers/rest';
-
-class BlogPage extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- blogItems: []
- };
- this.like = _.bind(this.like, this);
- }
- like(_id) {
- const {blogItems} = this.state;
-
- fetchMetaInfo(_id, update, blogItems,
- (newBlogItems) => this.setState({blogItems: newBlogItems})
- );
- }
- componentDidMount() {
- fetchPosts(
- (blogItems) => this.setState({blogItems})
- );
- }
- render() {
- const {blogItems} = this.state,
- columns = _.map(
- blogItems,
- item => [item.title, item.metaInfo.likes]
- );
-
- return (
-
- );
- }
-}
+import BlogListContainer from '../containers/BlogListContainer';
+import PieChartContainer from '../containers/PieChartContainer';
+
+const BlogPage = () => (
+
+);
export default BlogPage;
diff --git a/src/components/BlogPost.js b/src/components/BlogPost.js
index f8ca148..1256f6b 100644
--- a/src/components/BlogPost.js
+++ b/src/components/BlogPost.js
@@ -1,52 +1,26 @@
import React, {PropTypes} from 'react';
-
-import _ from 'lodash';
-
-import {updateItemMetaInfo as update} from 'helpers/like';
-
import BlogItem from 'components/widgets/blog/BlogItem';
-
-import {fetchPost, fetchMetaInfo} from 'helpers/rest';
-
import {Item} from 'semantic-ui-react';
+import {isEmpty} from 'lodash/lang';
-class BlogPost extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- blogItem: {}
- };
- this.like = _.bind(this.like, this);
- }
- like(_id) {
- const {blogItem} = this.state;
+const BlogPost = ({blogItem}) => (
+
+ {!isEmpty(blogItem) && }
+
+);
- fetchMetaInfo(_id, update, blogItem,
- (newBlogItem) => this.setState({blogItem: newBlogItem})
- );
- }
- componentDidMount() {
- fetchPost(
- this.props.params.post_id,
- (blogItem) => this.setState({blogItem})
- );
- }
- render() {
- const {blogItem} = this.state;
+BlogPost.propTypes = {
+ blogItem: PropTypes.object
+};
- return (
-
- this.like(blogItem._id)}
- />
-
- );
+BlogPost.defaultProps = {
+ blogItem: {
+ _id: BlogItem.defaultProps._id,
+ image: BlogItem.defaultProps.image,
+ title: BlogItem.defaultProps.title,
+ description: BlogItem.defaultProps.description,
+ metaInfo: BlogItem.defaultProps.metaInfo
}
-}
-
-BlogPost.propTypes = {
- params: PropTypes.object
};
export default BlogPost;
diff --git a/src/components/elements/Error.js b/src/components/elements/Error.js
new file mode 100644
index 0000000..570cec9
--- /dev/null
+++ b/src/components/elements/Error.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import {Segment, Header} from 'semantic-ui-react';
+
+const Error = () => (
+
+ Oops! Some Error occured!
+
+);
+
+export default Error;
diff --git a/src/components/elements/Spinner.js b/src/components/elements/Spinner.js
new file mode 100644
index 0000000..66a007a
--- /dev/null
+++ b/src/components/elements/Spinner.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import {Segment, Header} from 'semantic-ui-react';
+
+const Spinner = () => (
+
+
+
+);
+
+export default Spinner;
diff --git a/src/components/widgets/blog/BlogItem.js b/src/components/widgets/blog/BlogItem.js
index 7333254..78df0cd 100644
--- a/src/components/widgets/blog/BlogItem.js
+++ b/src/components/widgets/blog/BlogItem.js
@@ -1,59 +1,45 @@
import React, {PropTypes} from 'react';
-
import _ from 'lodash';
-
import Image from './elements/Image';
import TextBox from './elements/TextBox';
import MetaInfo from './elements/MetaInfo';
-import Like from './elements/Like';
+import LikeContainer from '../../../containers/LikeContainer';
import Title from './elements/Title';
-
import {Grid} from 'semantic-ui-react';
-class BlogItem extends React.Component {
- render() {
- const {image, description, metaInfo, title, _id, like} = this.props;
- return (
- React.createElement(
- Grid,
- {
- centered: true,
- columns: 3
- },
- React.createElement(
- Grid.Column,
- {
- computer: 4,
- tablet: 8,
- mobile: 15
- },
- React.createElement(Image, {
- src: image.src,
- alt: image.alt,
- width: '100%',
- height: '100%'
- })
- ),
- React.createElement(
- Grid.Column,
- {
- computer: 12,
- tablet: 8,
- mobile: 15
- },
- React.createElement(Title, {title, _id}),
- React.createElement(TextBox, {description}),
- React.createElement(MetaInfo, {
- author: metaInfo.author,
- created: metaInfo.created,
- modified: metaInfo.modified
- }),
- React.createElement(Like, {likes: metaInfo.likes, like})
- )
- )
- );
- }
-}
+const BlogItem = ({image, description, metaInfo, title, _id}) => (
+
+
+
+
+
+
+
+
+
+
+
+);
BlogItem.propTypes = {
_id: Title.propTypes._id,
@@ -64,9 +50,8 @@ BlogItem.propTypes = {
title: Title.propTypes.title,
description: TextBox.propTypes.description,
metaInfo: PropTypes.shape(
- _.assign({}, MetaInfo.propTypes, _.pick(Like.propTypes, ['likes']))
- ),
- like: Like.propTypes.like
+ _.assign({}, MetaInfo.propTypes, _.pick(LikeContainer.propTypes, ['likes']))
+ )
};
BlogItem.defaultProps = {
@@ -77,9 +62,8 @@ BlogItem.defaultProps = {
metaInfo: _.assign(
{},
MetaInfo.defaultProps,
- _.pick(Like.defaultProps, ['likes'])
- ),
- like: Like.defaultProps.like
+ _.pick(LikeContainer.defaultProps, ['likes'])
+ )
};
export default BlogItem;
diff --git a/src/components/widgets/blog/BlogList.js b/src/components/widgets/blog/BlogList.js
index 476a751..fe9deab 100644
--- a/src/components/widgets/blog/BlogList.js
+++ b/src/components/widgets/blog/BlogList.js
@@ -1,45 +1,28 @@
import React, {PropTypes} from 'react';
-import _ from 'lodash';
-
+import {map} from 'lodash/collection';
import BlogItem from './BlogItem';
-
import {List, Segment} from 'semantic-ui-react';
-const BlogList = ({blogItems, like}) => (
- React.createElement(List, null, _.map(blogItems, (blogItem) => (
- React.createElement(
- List.Item,
- {key: blogItem._id},
- React.createElement(
- Segment,
- {
- compact: true
- },
- React.createElement(
- BlogItem,
- _.assign(
- {},
- blogItem,
- {
- like: () => like(blogItem._id)
- }
- )
- )
- )
- )
- )))
-);
+const BlogList = ({blogItems}) => {
+ return (
+ {map(blogItems, item => (
+
+
+
+
+
+ ))}
+
);
+};
BlogList.propTypes = {
blogItems: PropTypes.arrayOf(
- PropTypes.shape(_.omit(BlogItem.propTypes, ['like']))
- ),
- like: BlogItem.propTypes.like
+ PropTypes.shape(BlogItem.propTypes)
+ )
};
BlogList.defaultProps = {
- blogItems: [_.omit(BlogItem.defaultProps, ['like'])],
- like: BlogItem.defaultProps.like
+ blogItems: [BlogItem.defaultProps]
};
export default BlogList;
diff --git a/src/components/widgets/blog/PieChart.js b/src/components/widgets/blog/PieChart.js
index 5f4f966..944659a 100644
--- a/src/components/widgets/blog/PieChart.js
+++ b/src/components/widgets/blog/PieChart.js
@@ -1,5 +1,4 @@
import React, {PropTypes} from 'react';
-
import c3 from 'c3';
import {Segment} from 'semantic-ui-react';
diff --git a/src/components/widgets/blog/elements/Like.js b/src/components/widgets/blog/elements/Like.js
index a3e59ff..0dc9c90 100644
--- a/src/components/widgets/blog/elements/Like.js
+++ b/src/components/widgets/blog/elements/Like.js
@@ -2,7 +2,7 @@ import React, {PropTypes} from 'react';
import {Segment, Button} from 'semantic-ui-react';
-const Like = ({likes, like}) => (
+const Like = ({likes, like, id}) => (
React.createElement(
Segment,
{vertical: true, textAlign: 'right', basic: true},
@@ -16,7 +16,7 @@ const Like = ({likes, like}) => (
content: likes
},
labelPosition: 'right',
- onClick: () => like()
+ onClick: () => like(id)
}
)
)
@@ -24,12 +24,14 @@ const Like = ({likes, like}) => (
Like.propTypes = {
likes: PropTypes.number,
- like: PropTypes.func
+ like: PropTypes.func,
+ id: PropTypes.string
};
Like.defaultProps = {
likes: 0,
- like: () => true
+ like: () => true,
+ id: '676hjh67'
};
export default Like;
diff --git a/src/constants/API.js b/src/constants/API.js
new file mode 100644
index 0000000..272bdfe
--- /dev/null
+++ b/src/constants/API.js
@@ -0,0 +1 @@
+export const API_ROOT = 'http://localhost:3001';
diff --git a/src/constants/actionTypes/PostActionTypes.js b/src/constants/actionTypes/PostActionTypes.js
new file mode 100644
index 0000000..b275b90
--- /dev/null
+++ b/src/constants/actionTypes/PostActionTypes.js
@@ -0,0 +1,3 @@
+export const FETCH_POST_REQUEST = 'FETCH_POST_REQUEST';
+export const FETCH_POST_SUCCESS = 'FETCH_POST_SUCCESS';
+export const FETCH_POST_ERROR = 'FETCH_POST_ERROR';
diff --git a/src/constants/actionTypes/PostsActionTypes.js b/src/constants/actionTypes/PostsActionTypes.js
new file mode 100644
index 0000000..54efa4f
--- /dev/null
+++ b/src/constants/actionTypes/PostsActionTypes.js
@@ -0,0 +1,3 @@
+export const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST';
+export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS';
+export const FETCH_POSTS_ERROR = 'FETCH_POSTS_ERROR';
diff --git a/src/constants/actionTypes/actionTypes.js b/src/constants/actionTypes/actionTypes.js
new file mode 100644
index 0000000..9f36e8c
--- /dev/null
+++ b/src/constants/actionTypes/actionTypes.js
@@ -0,0 +1,2 @@
+export const REQUEST_INCREMENT_LIKE = 'REQUEST_INCREMENT_LIKE';
+
diff --git a/src/containers/BlogListContainer.js b/src/containers/BlogListContainer.js
new file mode 100644
index 0000000..db61907
--- /dev/null
+++ b/src/containers/BlogListContainer.js
@@ -0,0 +1,8 @@
+import BlogList from '../components/widgets/blog/BlogList';
+import {connect} from 'react-redux';
+
+const stateToProps = (state) => ({
+ blogItems: state.posts.entries
+});
+
+export default connect(stateToProps)(BlogList);
diff --git a/src/containers/DevTools.js b/src/containers/DevTools.js
new file mode 100644
index 0000000..c3455bd
--- /dev/null
+++ b/src/containers/DevTools.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import { createDevTools } from 'redux-devtools';
+import LogMonitor from 'redux-devtools-log-monitor';
+import DockMonitor from 'redux-devtools-dock-monitor';
+
+//Use Ctrl+H to toggle DockMonitor visibility
+const DevTools = createDevTools(
+
+
+
+);
+
+export default DevTools;
diff --git a/src/containers/LikeContainer.js b/src/containers/LikeContainer.js
new file mode 100644
index 0000000..7f0ca71
--- /dev/null
+++ b/src/containers/LikeContainer.js
@@ -0,0 +1,9 @@
+import {connect} from 'react-redux';
+import Like from '../components/widgets/blog/elements/Like';
+import {incrementLike} from '../actions/actionCreators';
+
+const actionsToProps = (dispatch, ownProps) => ({
+ like: () => dispatch(incrementLike(ownProps.id))
+});
+
+export default connect(null, actionsToProps)(Like);
diff --git a/src/containers/PieChartContainer.js b/src/containers/PieChartContainer.js
new file mode 100644
index 0000000..e5a046d
--- /dev/null
+++ b/src/containers/PieChartContainer.js
@@ -0,0 +1,9 @@
+import {connect} from 'react-redux';
+import PieChart from '../components/widgets/blog/PieChart';
+import {map} from 'lodash/collection';
+
+const stateToProps = (state) => ({
+ columns: map(state.posts.entries, item => [item.title, item.metaInfo.likes])
+});
+
+export default connect(stateToProps)(PieChart);
diff --git a/src/containers/PostContainer.js b/src/containers/PostContainer.js
new file mode 100644
index 0000000..ccdd342
--- /dev/null
+++ b/src/containers/PostContainer.js
@@ -0,0 +1,8 @@
+import {connect} from 'react-redux';
+import BlogPost from '../components/BlogPost';
+
+const stateToProps = (state) => ({
+ blogItem: state.post.entry
+});
+
+export default connect(stateToProps)(BlogPost);
diff --git a/src/helpers/like.js b/src/helpers/like.js
deleted file mode 100644
index 8c45f73..0000000
--- a/src/helpers/like.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import _ from 'lodash';
-import update from 'immutability-helper';
-
-export const updateItemsMetaInfo = (blogItems, metaInfo, setNewState, _id) => {
- const index = _.findIndex(blogItems, {_id});
-
- const newBlogItems = update(
- blogItems,
- {[index]: {$merge: {metaInfo}}}
- );
-
- setNewState(newBlogItems);
-};
-
-export const updateItemMetaInfo = (blogItem, metaInfo, setNewState) => {
- const newBlogItem = update(
- blogItem,
- {$merge: {metaInfo}}
- );
-
- setNewState(newBlogItem);
-};
diff --git a/src/helpers/prepareData.js b/src/helpers/prepareData.js
new file mode 100644
index 0000000..efc8c0f
--- /dev/null
+++ b/src/helpers/prepareData.js
@@ -0,0 +1,16 @@
+import {parse} from 'qs';
+import {compact} from 'lodash/array';
+import {map} from 'lodash/collection';
+
+export default (store, state) => {
+ const {location, params, routes} = state;
+
+ const query = parse(location.search, {ignoreQueryPrefix: true});
+
+ const prepareDataFns = compact(map(routes, route => route.prepareData));
+
+ map(
+ prepareDataFns,
+ prepareData => prepareData(store, query, params, location)
+ );
+};
diff --git a/src/helpers/rest.js b/src/helpers/rest.js
deleted file mode 100644
index 871b49d..0000000
--- a/src/helpers/rest.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import request from 'superagent';
-
-export const fetchPosts = (setNewState) => {
- request.get(
- 'http://localhost:3001/posts',
- {},
- (err, res) => setNewState(res.body)
- );
-};
-
-export const fetchPost = (_id, setNewState) => {
- request.get(
- `http://localhost:3001/posts/${_id}`,
- {},
- (err, res) => setNewState(res.body)
- );
-};
-
-export const fetchMetaInfo = (_id, update, state, setNewState) => {
- request.get(
- `http://localhost:3001/metainfo/${_id}`,
- {},
- (err, res) => update(state, res.body, setNewState, _id)
- );
-};
diff --git a/src/index.js b/src/index.js
index 059aeb6..4088c0b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,11 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
-
import {AppContainer} from 'react-hot-loader';
-
import '../semantic/dist/semantic.min.css';
import 'c3/c3.min.css';
-
import App from './App';
const rootEl = document.getElementById('app');
diff --git a/src/reducers/Post.js b/src/reducers/Post.js
new file mode 100644
index 0000000..33c7953
--- /dev/null
+++ b/src/reducers/Post.js
@@ -0,0 +1,32 @@
+import {assign} from 'lodash/object';
+import {isEmpty} from 'lodash/lang';
+import * as types from '../constants/actionTypes/PostActionTypes';
+import update from 'immutability-helper';
+import {REQUEST_INCREMENT_LIKE} from '../constants/actionTypes/actionTypes';
+
+const initialState = {
+ isFetching: false,
+ error: false,
+ entry: {}
+};
+
+export default (state = initialState, action) => {
+ switch (action.type) {
+ case types.FETCH_POST_REQUEST:
+ return assign({}, initialState, {isFetching: true});
+ case types.FETCH_POST_ERROR:
+ return assign({}, initialState, {error: true});
+ case types.FETCH_POST_SUCCESS:
+ return assign({}, initialState, {entry: action.response});
+ case REQUEST_INCREMENT_LIKE:
+ return isEmpty(state.entry) ? state : update(state, {
+ entry: {
+ metaInfo: {
+ likes: {$apply: (count) => (count + 1)}
+ }
+ }
+ });
+ default:
+ return state;
+ }
+};
diff --git a/src/reducers/Posts.js b/src/reducers/Posts.js
new file mode 100644
index 0000000..be7c043
--- /dev/null
+++ b/src/reducers/Posts.js
@@ -0,0 +1,37 @@
+import {assign} from 'lodash/object';
+import {isEmpty} from 'lodash/lang';
+import * as types from '../constants/actionTypes/PostsActionTypes';
+import {map} from 'lodash/collection';
+import update from 'immutability-helper';
+import {REQUEST_INCREMENT_LIKE} from '../constants/actionTypes/actionTypes';
+
+const initialState = {
+ isFetching: false,
+ error: false,
+ entries: []
+};
+
+const incrementLikes = (state, action) => (
+ state._id !== action.id ? state :
+ update(state, {
+ metaInfo: {likes: {$apply: (count) => (count + 1)}}
+ })
+);
+
+export default (state = initialState, action) => {
+ switch (action.type) {
+ case types.FETCH_POSTS_REQUEST:
+ return assign({}, initialState, {isFetching: true});
+ case types.FETCH_POSTS_ERROR:
+ return assign({}, initialState, {error: true});
+ case types.FETCH_POSTS_SUCCESS:
+ return assign({}, initialState, {entries: action.response});
+ case REQUEST_INCREMENT_LIKE:
+ return isEmpty(state.entries) ? state : assign({}, state, {
+ entries: map(state.entries,
+ (post) => incrementLikes(post, action))
+ });
+ default:
+ return state;
+ }
+};
diff --git a/src/reducers/index.js b/src/reducers/index.js
new file mode 100644
index 0000000..0a053d0
--- /dev/null
+++ b/src/reducers/index.js
@@ -0,0 +1,9 @@
+import {combineReducers} from 'redux';
+
+import posts from './Posts.js';
+import post from './Post.js';
+
+export default combineReducers({
+ posts,
+ post
+});
diff --git a/src/routes/Blog.js b/src/routes/Blog.js
index a2c8fbb..b4e9549 100644
--- a/src/routes/Blog.js
+++ b/src/routes/Blog.js
@@ -1,16 +1,24 @@
-import BlogPage from 'components/BlogPage';
import MainLayout from 'components/layouts/MainLayout';
-import BlogPost from 'components/BlogPost';
import {postsPath} from 'helpers/routes';
+import {fetchPosts} from '../actions/Posts';
+import {fetchPost} from '../actions/Post';
+import BlogPage from '../components/BlogPage';
+import PostContainer from '../containers/PostContainer';
const Index = {
path: '/',
- component: BlogPage
+ component: BlogPage,
+ prepareData: (store) => {
+ store.dispatch(fetchPosts());
+ }
};
const PostRoute = {
path: postsPath(),
- component: BlogPost
+ component: PostContainer,
+ prepareData: (store, query, params) => {
+ store.dispatch(fetchPost(params.post_id));
+ }
};
export default {
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..c783a20
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,14 @@
+import {createStore, applyMiddleware, compose} from 'redux';
+import thunk from 'redux-thunk';
+import reducers from 'reducers';
+import DevTools from '../containers/DevTools';
+
+const store = createStore(
+ reducers,
+ compose(
+ applyMiddleware(thunk),
+ DevTools.instrument()
+ )
+);
+
+export default store;