diff --git a/src/ac/index.js b/src/ac/index.js index b93bb22..81b24ad 100644 --- a/src/ac/index.js +++ b/src/ac/index.js @@ -8,7 +8,8 @@ import { LOAD_ARTICLE, SUCCESS, FAIL, - START + START, + LOAD_COMMENTS } from '../constants' import { fetchData } from './service' @@ -88,3 +89,28 @@ export function loadArticle(id) { ) } } + +export function loadComments(articleId) { + return (dispatch) => { + dispatch({ + type: LOAD_COMMENTS + START, + payload: { articleId } + }) + + fetchData(`comment?article=${articleId}`) + .then((response) => + dispatch({ + type: LOAD_COMMENTS + SUCCESS, + payload: { articleId }, + response + }) + ) + .catch((error) => + dispatch({ + type: LOAD_COMMENTS + FAIL, + payload: { articleId }, + error + }) + ) + } +} diff --git a/src/components/comment-list/index.js b/src/components/comment-list/index.js index ebe51f9..5224f32 100644 --- a/src/components/comment-list/index.js +++ b/src/components/comment-list/index.js @@ -5,13 +5,36 @@ import Comment from '../comment' import CommentForm from '../comment-form' import toggleOpen from '../../decorators/toggleOpen' import './style.css' +import { loadComments } from '../../ac' +import { + loadingCommentsSelector, + isCommentsLoadedSelector +} from '../../selectors' +import { connect } from 'react-redux' +import Loader from '../common/loader' class CommentList extends Component { static propTypes = { article: PropTypes.object.isRequired, //from toggleOpen decorator isOpen: PropTypes.bool, - toggleOpen: PropTypes.func + toggleOpen: PropTypes.func, + loadComments: PropTypes.func, + isCommentsLoaded: PropTypes.bool, + loading: PropTypes.bool + } + + componentDidUpdate(oldProps) { + const { + isOpen, + isCommentsLoaded, + article, + loadComments, + loading + } = this.props + + if (!oldProps.isOpen && isOpen && !loading && !isCommentsLoaded) + loadComments(article.id) } render() { @@ -36,13 +59,16 @@ class CommentList extends Component { getBody() { const { article: { comments = [], id }, - isOpen + isOpen, + loading, + isCommentsLoaded } = this.props if (!isOpen) return null + if (loading) return return (
- {comments.length ? ( + {comments.length && isCommentsLoaded ? ( this.getComments() ) : (

No comments yet

@@ -65,4 +91,15 @@ class CommentList extends Component { } } -export default toggleOpen(CommentList) +const createMapStateToProps = () => { + const isCommentsLoaded = isCommentsLoadedSelector() + + return (state, ownProps) => ({ + loading: loadingCommentsSelector(state), + isCommentsLoaded: isCommentsLoaded(state, ownProps) + }) +} + +export default toggleOpen( + connect(createMapStateToProps, { loadComments })(CommentList) +) diff --git a/src/constants/index.js b/src/constants/index.js index 6266326..9ef403c 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -8,6 +8,7 @@ export const CHANGE_SELECTION = 'CHANGE_SELECTION' export const CHANGE_DATE_RANGE = 'CHANGE_DATE_RANGE' export const ADD_COMMENT = 'ADD_COMMENT' +export const LOAD_COMMENTS = 'LOAD_COMMENTS' export const START = '_START' export const SUCCESS = '_SUCCESS' diff --git a/src/reducer/comments.js b/src/reducer/comments.js index e4645f7..c24ac2f 100644 --- a/src/reducer/comments.js +++ b/src/reducer/comments.js @@ -1,16 +1,48 @@ -import { ADD_COMMENT } from '../constants' -import { normalizedComments } from '../fixtures' +import { ADD_COMMENT, LOAD_COMMENTS, START, SUCCESS, FAIL } from '../constants' import { arrToMap } from './utils' +import { Record } from 'immutable' -export default (state = arrToMap(normalizedComments), action) => { - const { type, payload, randomId } = action +const CommentModel = new Record({ + id: null, + user: null, + text: null +}) + +const ReducerRecord = new Record({ + entities: arrToMap([], CommentModel), + loading: false, + error: null +}) + +export default (state = new ReducerRecord(), action) => { + const { type, payload, randomId, response } = action switch (type) { case ADD_COMMENT: - return state.set(randomId, { - ...payload.comment, - id: randomId - }) + return state.set( + 'entities', + state.get('entities').set( + randomId, + new CommentModel({ + ...payload.comment, + id: randomId + }) + ) + ) + + case LOAD_COMMENTS + SUCCESS: + return state + .set( + 'entities', + state.get('entities').merge(arrToMap(response, CommentModel)) + ) + .set('loading', false) + + case LOAD_COMMENTS + START: + return state.set('loading', true) + + case LOAD_COMMENTS + FAIL: + return state.set('loading', false).set('error', action.error) default: return state diff --git a/src/selectors/index.js b/src/selectors/index.js index 916c01d..e5d0c93 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -12,7 +12,11 @@ export const articleListSelector = createSelector( export const loadingArticlesSelector = (state) => state.articles.loading const filtersSelector = (state) => state.filters -const commentListSelector = (state) => state.comments +const commentMapSelector = (state) => state.comments.entities +const commentListSelector = createSelector(commentMapSelector, (commentsMap) => + commentsMap.valueSeq().toArray() +) + export const filtersSelectionSelector = createSelector( filtersSelector, (filters) => filters.selected @@ -40,6 +44,22 @@ export const filtratedArticlesSelector = createSelector( export const createCommentSelector = () => { return createSelector(commentListSelector, idSelector, (comments, id) => { - return comments.get(id) + return comments.find((comment) => comment.id === id) }) } + +const commentIdsSelector = (_, props) => props.article.comments + +export const isCommentsLoadedSelector = () => { + return createSelector( + commentListSelector, + commentIdsSelector, + (comments, commentIds) => { + return commentIds.every((commentId) => + comments.find((comment) => comment.id === commentId) + ) + } + ) +} + +export const loadingCommentsSelector = (state) => state.comments.loading