Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# basic-react-1902

## HT1.1 Сделать список комментариев, открывать/закрывать по нажатию на кнопку с помощью декоратора
## HT1.2 Подключить календарь с выбором диапазона дат, отображать диапазон текстом(https://github.com/gpbl/react-day-picker)
## HT1.3 для всего написать propTypes
Expand All @@ -11,4 +13,5 @@
## HT4.1 Сделать форму добавления коммента в CommentList
## HT4.2 Переписать articles редюсер на хранилище id -> статья(аналогично comments)
## HT4.3 Написать мидлвару для генерации случайных id
## HT4.4 Реализовать добавление коммента к статье
## HT4.4 Реализовать добавление коммента к статье

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"prop-types": "^15.6.0",
"randomstring": "^1.1.5",
"react": "^16.2.0",
"react-addons-css-transition-group": "^15.6.2",
"react-day-picker": "^7.0.7",
Expand All @@ -14,6 +15,7 @@
"react-scripts": "1.1.1",
"react-select": "^1.2.1",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"reselect": "^3.0.1"
},
"scripts": {
Expand Down
25 changes: 24 additions & 1 deletion src/AC/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {INCREMENT, DELETE_ARTICLE, CHANGE_DATE_RANGE, CHANGE_SELECTION} from '../constants'
import {
INCREMENT, DELETE_ARTICLE, CHANGE_DATE_RANGE, CHANGE_SELECTION, ADD_NEW_COMMENT,
UPDATE_ARTICLE_COMMENTS
} from '../constants'

export function increment() {
return {
Expand Down Expand Up @@ -26,3 +29,23 @@ export function changeSelection(selected) {
payload: { selected }
}
}

export function addNewComment(user, text, articleId) {
return {
type: ADD_NEW_COMMENT,
payload: {
data: {user, text},
articleId
}
};
}

export function updateArticleComments(articleId, commentId) {
return {
type: UPDATE_ARTICLE_COMMENTS,
payload: {
articleId,
commentId
}
}
}
79 changes: 79 additions & 0 deletions src/components/add-new-comment-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addNewComment } from '../AC';

class AddNewCommentForm extends Component {

state = {
userName: '',
text: ''
};

static propTypes = {
articleId: PropTypes.string.isRequired,
addComment: PropTypes.func.isRequired
};

static defaultProps = {};

handleSubmit = (e) => {
e.preventDefault();
this.props.addComment(this.state.userName, this.state.text, this.props.articleId);
this.setState({
userName: '',
text: ''
})
};

handleUsernameInput = (e) => {
this.setState({
userName: e.target.value
});
};

handleTextInput = (e) => {
this.setState({
text: e.target.value
});
};

render() {
return (
<div style={{border: '1px solid #000', padding: '5px', marginTop: '10px'}}>
<form onSubmit={this.handleSubmit}>
<div>
<label htmlFor='username'>Username</label><br />
<input type='text' name='username' id='username' placeholder='Input your name' value={this.state.userName} onChange={this.handleUsernameInput} />
<br /><br />
</div>
<div>
<label htmlFor='text'>Text</label><br />
<textarea name='text' id='text' placeholder='Input text' value={this.state.text} onChange={this.handleTextInput} />
<br /><br />
</div>
<div>
<input type="submit" value="Add" disabled={!(this.state.userName && this.state.text)}/>
</div>
</form>
</div>
);
}

}

const mapStateToProps = (state, ownProps) => {
return {
articleId: ownProps.articleId
}
};

const mapDispatchToProps = (dispatch) => {
return {
addComment(user, text, articleId) {
dispatch(addNewComment(user, text, articleId));
}
}
};

export default connect(mapStateToProps, mapDispatchToProps)(AddNewCommentForm);
31 changes: 16 additions & 15 deletions src/components/article-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,30 @@ import { filtratedArticles } from '../../selectors'

export class ArticleList extends Component {
static propTypes = {
articles: PropTypes.array.isRequired,
articles: PropTypes.object.isRequired,

//from accordion decorator
openItemId: PropTypes.string,
toggleItem: PropTypes.func
};

componentDidMount() {
/*componentDidMount() {
this.props.fetchData && this.props.fetchData()
}
}*/

render() {
const { articles, openItemId, toggleItem } = this.props
console.log('---', 'rendering ArticlList')
const articleElements = articles.map(article =>
<li key = {article.id} className = "test__article-list--item">
<Article
article = {article}
onButtonClick = {toggleItem}
isOpen = {openItemId === article.id}
/>
</li>
)
const { articles, openItemId, toggleItem } = this.props;
console.log('---', 'rendering ArticleList');
const articleElements = Object.keys(articles).map((articleId) => {
return (<li key = {articleId} className = "test__article-list--item">
<Article
commentsLength = {articles[articleId].comments ? articles[articleId].comments.length : 0}
article = {articles[articleId]}
onButtonClick = {toggleItem}
isOpen = {openItemId === articleId}
/>
</li>);
});
return (
<ul>
{articleElements}
Expand All @@ -43,4 +44,4 @@ export default connect(state => {
return {
articles: filtratedArticles(state)
}
})(accordion(ArticleList))
})(accordion(ArticleList))
25 changes: 16 additions & 9 deletions src/components/article/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {PureComponent, Fragment} from 'react'
import React, {PureComponent, Fragment, Component} from 'react'
import PropTypes from 'prop-types'
import CSSTransition from 'react-addons-css-transition-group'
import { connect } from 'react-redux'
Expand All @@ -11,15 +11,25 @@ class Article extends PureComponent {
error: null
}

/*componentWillReceiveProps(nextProps) {
console.log('WillReceive Props => ', nextProps);
}*/

/*shouldComponentUpdate(nextProps, nextState) {
console.log('ShouldUpdate => ', nextProps);
return true;
}*/

componentDidCatch(error) {
console.log('---', error)
this.setState({ error })
}

render() {
if (this.state.error) return <h2>{this.state.error.message}</h2>

const { isOpen, article, onButtonClick } = this.props
console.log('---Article is rendering');

return (
<Fragment>
<h2>
Expand Down Expand Up @@ -58,19 +68,16 @@ function getBody(article) {
return (
<section className = "test__article--body">
{article.text}
<CommentList comments={article.comments}/>
<CommentList comments={article.comments} articleId={article.id}/>
</section>
)
}


Article.propTypes = {
isOpen: PropTypes.bool,
article: PropTypes.shape({
title: PropTypes.string.isRequired,
text: PropTypes.string
}).isRequired,
article: PropTypes.object.isRequired,
onButtonClick: PropTypes.func
}
};

export default connect(null, { deleteArticle })(Article)
export default connect(null, { deleteArticle })(Article)
8 changes: 6 additions & 2 deletions src/components/comment-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import CSSTransition from 'react-addons-css-transition-group'
import Comment from '../comment'
import toggleOpen from '../../decorators/toggleOpen'
import AddNewCommentForm from '../add-new-comment-form';
import './style.css'

class CommentList extends Component {
Expand All @@ -14,7 +15,8 @@ class CommentList extends Component {
comments: PropTypes.array.isRequired,
//from toggleOpen decorator
isOpen: PropTypes.bool,
toggleOpen: PropTypes.func
toggleOpen: PropTypes.func,
articleId: PropTypes.string.isRequired
}

render() {
Expand Down Expand Up @@ -45,6 +47,7 @@ class CommentList extends Component {
? this.getComments()
: <h3 className="test__comment-list--empty">No comments yet</h3>
}
<AddNewCommentForm articleId={this.props.articleId} />
</div>
)
}
Expand All @@ -59,9 +62,10 @@ class CommentList extends Component {
</li>)
}
</ul>

)
}
}


export default toggleOpen(CommentList)
export default toggleOpen(CommentList)
14 changes: 7 additions & 7 deletions src/components/filters/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import 'react-select/dist/react-select.css'

class SelectFilter extends Component {
static propTypes = {
articles: PropTypes.array.isRequired
articles: PropTypes.object.isRequired
};

handleChange = selected => this.props.changeSelection(selected.map(option => option.value))

render() {
const { articles, selected } = this.props
const options = articles.map(article => ({
label: article.title,
value: article.id
}))
const { articles, selected } = this.props;
const options = Object.keys(articles).map(articleId => ({
label: articles[articleId].title,
value: articleId
}));

return <Select
options={options}
Expand All @@ -32,4 +32,4 @@ class SelectFilter extends Component {
export default connect(state => ({
selected: state.filters.selected,
articles: state.articles
}), { changeSelection })(SelectFilter)
}), { changeSelection })(SelectFilter)
11 changes: 7 additions & 4 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export const INCREMENT = 'INCREMENT'
export const INCREMENT = 'INCREMENT';

export const DELETE_ARTICLE = 'DELETE_ARTICLE'
export const DELETE_ARTICLE = 'DELETE_ARTICLE';
export const UPDATE_ARTICLE_COMMENTS = 'UPDATE_ARTICLE_COMMENTS';

export const CHANGE_SELECTION = 'CHANGE_SELECTION'
export const CHANGE_DATE_RANGE = 'CHANGE_DATE_RANGE'
export const CHANGE_SELECTION = 'CHANGE_SELECTION';
export const CHANGE_DATE_RANGE = 'CHANGE_DATE_RANGE';

export const ADD_NEW_COMMENT = 'ADD_NEW_COMMENT';
16 changes: 16 additions & 0 deletions src/middlewares/handle-new-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ADD_NEW_COMMENT } from '../constants';
import { updateArticleComments } from '../AC';
import randomString from 'randomstring';

export const handleNewComment = (store) => (next) => (action) => {
if (action.type === ADD_NEW_COMMENT) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

через мидлвары будет проходить каждый экшин, они должны быть максимально общими, завязывать на конкретные экшины - не лучшее решение

let newAction = {...action};
let commentId = randomString.generate(12);
newAction.payload.data.id = commentId;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше не мутировать payload, мало-ли что там станут передавать

let result = next(newAction);
store.dispatch(updateArticleComments(newAction.payload.articleId, commentId));
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем тебе 2 диспатча?

return result;
}

return next(action);
};
29 changes: 24 additions & 5 deletions src/reducer/articles.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import { normalizedArticles as defaultArticles } from '../fixtures'
import { DELETE_ARTICLE } from '../constants'
import { normalizedArticles } from '../fixtures';
import { DELETE_ARTICLE, UPDATE_ARTICLE_COMMENTS } from '../constants';

const defaultArticles = normalizedArticles.reduce((acc, article) => {
return {
...acc,
[article.id]: article
}
}, {});

export default (articlesState = defaultArticles, action) => {
const { type, payload } = action
const { type, payload } = action;

switch (type) {
case DELETE_ARTICLE:
return articlesState.filter(article => article.id !== payload.id)
let articlesForDelete = {...articlesState};
delete articlesForDelete[payload.id];
return articlesForDelete;

case UPDATE_ARTICLE_COMMENTS:
let articlesForUpdate = {...articlesState};
let article = articlesForUpdate[payload.articleId];
if (!article.comments) {
article.comments = [];
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не мутируй стейт

}
article.comments.push(payload.commentId);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

и здесь не мутируй

articlesForUpdate[payload.articleId] = article;
return articlesForUpdate;

default:
return articlesState
}
}
}
Loading