Что такое добавление новости?
У данной задачи есть масса вариантов решения. Мы начнем с канонического варианта: в общем родителе (<App/>
) будем хранить state с новостями. В компонент <Add />
будем передавать функцию (так как в props мы можем передавать что угодно), которая будет иметь доступ к state с новостями и которая в свою очередь будет добавлять новость в этот state.
Так как state у <App />
будет изменяться, все дети (в том числе и новостная лента <News />
) будут перерисованы, а следовательно - мы увидим добавленную новость.
Шаг 1: создадим состояние с новостями в <App />
(а следовательно, переделаем App из stateless в statefull):
class App extends React.Component { state = { news: myNews, // в начальное состояние положили значение из переменной } render() { return ( <React.Fragment> <Add /> <h3>Новости</h3> {/* считали новости из this.state */} <News data={this.state.news} /> </React.Fragment> ) } }
Что примечательно, мы изменили источник данных для <News />
, но компонент работает как ни в чем не бывало. Удобно!
Шаг 2: передадим функцию-обработчик в Add
class App extends React.Component { state = { news: myNews, } handleAddNews = () => { console.log('я вызвана из Add, но имею доступ к this.state у App!', this.state) } render() { return ( <React.Fragment> <Add onAddNews={this.handleAddNews} /> <h3>Новости</h3> <News data={this.state.news} /> </React.Fragment> ) } }
Шаг 3: вызовем функцию из <Add />
, не забудем про PropTypes.
class Add extends React.Component { state = { name: '', text: '', agree: false, } onBtnClickHandler = (e) => { e.preventDefault() const { name, text } = this.state // alert(name + '\n' + text) // вызываем вместо alert this.props.onAddNews() } ... render() { const { name, text, agree } = this.state return ( <form className='add'> ... <button className='add__btn' onClick={this.onBtnClickHandler} disabled={!this.validate()}> Показать alert </button> </form> ) } } Add.propTypes = { onAddNews: PropTypes.func.isRequired, // func используется для проверки передачи function }
Проверим:
Шаг 4: из <Add />
будем передавать объект с новостью.
class Add extends React.Component { ... onBtnClickHandler = (e) => { e.preventDefault() const { name, text } = this.state // передаем name и text // big text у нас отсутствует :( this.props.onAddNews({ name, text }) } ... }
Шаг 5: полученный объект будем записывать на первое место в массиве с новостями в <App />
. Разумеется, массив будем обновлять через setState.
class App extends React.Component { ... handleAddNews = (data) => { // сначала мы формируем массив, на основе // всего того, что уже было в новостях // и кладем это все в новый массив + // новую новость кладем в начало массива const nextNews = [data, ...this.state.news] // затем обновляем новый массив новостей в this.state.news this.setState({ news: nextNews }) } ... }
Проверим?
Окей, перед нами отличный кейс (реальный рабочий случай).
Во-первых: мы были почти прилежными учениками и сделали propTypes, для <Article />
. Из ошибки сразу понятно: мы не передаем значение author (потому что, мы передаем name).
Во-вторых: мы все же накосячили, и забыли добавить в propTypes для <Article />
свойство id. Хорошо, что кода мало и ошибка сразу нашлась. Не забывайте про перечисление всех свойств в propTypes
- это супер шпаргалка.
В-третьих: мы из <Add />
не передаем id
и bigText
.
Тем не менее, нельзя не отметить, что мы добавили динамики в наше приложение! Новость добавляется, причем счетчик новостей работает (а мы его вообще не трогали). Кто уже празднует - молодец, но кто хочет пофиксить все ошибки и сдать работу на пять - милости прошу в заключительный пункт основного курса.
Я бы хотел, чтобы все это вы пофиксили сами. Но, чтобы не быть автором, который "оп-па, косяк, давайте-ка я отдам это вам на домашку", я все сделаю и опишу. Просто попробуйте. У вас получится. Практика решает. Обязательно всегда практикуйтесь для закрепления материала.
Задача:
+new Date()
). Для обучающего примера будет достаточно;<News />
Решение:
Полный код того, что находится в <body />
(с комментариями о последних изменениях)
<body> <div id="root"></div> <script type="text/babel"> const myNews = [ { id: 1, author: 'Саша Печкин', text: 'В четверг, четвертого числа...', bigText: 'в четыре с четвертью часа четыре чёрненьких чумазеньких чертёнка чертили чёрными чернилами чертёж.' }, { id: 2, author: 'Просто Вася', text: 'Считаю, что $ должен стоить 35 рублей!', bigText: 'А евро 42!' }, { id: 3, author: 'Max Frontend', text: 'Прошло 2 года с прошлых учебников, а $ так и не стоит 35', bigText: 'А евро опять выше 70.' }, { id: 4, author: 'Гость', text: 'Бесплатно. Без смс, про реакт, заходи - https://maxpfrontend.ru', bigText: 'Еще есть группа VK, telegram и канал на youtube! Вся инфа на сайте, не реклама!' } ]; class Article extends React.Component { state = { visible: false, } handleReadMoreClck = (e) => { e.preventDefault() this.setState({ visible: true }) } render() { const { author, text, bigText } = this.props.data const { visible } = this.state return ( <div className='article'> <p className='news__author'>{author}:</p> <p className='news__text'>{text}</p> { !visible && <a onClick={this.handleReadMoreClck} href="#" className='news__readmore'>Подробнее</a> } { visible && <p className='news__big-text'>{bigText}</p> } </div> ) } } Article.propTypes = { data: PropTypes.shape({ id: PropTypes.number.isRequired, // добавили id, это число, обязательно author: PropTypes.string.isRequired, text: PropTypes.string.isRequired, bigText: PropTypes.string.isRequired }) } class News extends React.Component { // удалили старое состояние counter: 0 (старый ненужный код) renderNews = () => { const { data } = this.props let newsTemplate = null if (data.length) { newsTemplate = data.map(function(item) { return <Article key={item.id} data={item}/> }) } else { newsTemplate = <p>К сожалению новостей нет</p> } return newsTemplate } render() { const { data } = this.props return ( <div className='news'> {this.renderNews()} { data.length ? <strong className={'news__count'}>Всего новостей: {data.length}</strong> : null } </div> ); } } News.propTypes = { data: PropTypes.array.isRequired } class Add extends React.Component { state = { name: '', text: '', bigText: '', // добавлен bigText agree: false, } onBtnClickHandler = (e) => { e.preventDefault() const { name, text, bigText } = this.state // вытащили так же и bigText this.props.onAddNews({ id: +new Date(), // в id сохраняется количество миллисекунд прошедших с 1 января 1970 года в часовом поясе UTC author: name, // name сохраняем в поле author text, bigText, }) } handleChange = (e) => { const { id, value } = e.currentTarget this.setState({ [id]: e.currentTarget.value }) } handleCheckboxChange = (e) => { this.setState({ agree: e.currentTarget.checked }) } validate = () => { const { name, text, agree } = this.state if (name.trim() && text.trim() && agree) { return true } return false } render() { const { name, text, bigText, agree } = this.state return ( <form className='add'> <input id='name' type='text' onChange={this.handleChange} className='add__author' placeholder='Ваше имя' value={name} /> <textarea id='text' onChange={this.handleChange} className='add__text' placeholder='Текст новости' value={text} ></textarea> {/* добавили bigText */} <textarea id='bigText' onChange={this.handleChange} className='add__text' placeholder='Текст новости подробно' value={bigText} ></textarea> <label className='add__checkrule'> <input type='checkbox' onChange={this.handleCheckboxChange} /> Я согласен с правилами </label> <button className='add__btn' onClick={this.onBtnClickHandler} disabled={!this.validate()}> Показать alert </button> </form> ) } } Add.propTypes = { onAddNews: PropTypes.func.isRequired, } class App extends React.Component { state = { news: myNews, } handleAddNews = (data) => { const nextNews = [data, ...this.state.news] this.setState({ news: nextNews }) } render() { return ( <React.Fragment> <Add onAddNews={this.handleAddNews}/> <h3>Новости</h3> <News data={this.state.news}/> </React.Fragment> ) } } ReactDOM.render( <App />, document.getElementById('root') ); </script> </body>
Готово!
на данный момент.