Книга: react-course-ru-v2
Назад: Работа с формой
Дальше: Итоги по основам

Добавить новость

Добавить новость

Что такое добавление новости?

  1. Это форма, в которую мы вводим необходимые данные.
  2. Это "лента новостей", которая отображает наши данные.

У данной задачи есть масса вариантов решения. Мы начнем с канонического варианта: в общем родителе (<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 } 

Проверим:

add news log inside app

Шаг 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 })   }   ... } 

Проверим?

add news error

Окей, перед нами отличный кейс (реальный рабочий случай).

Во-первых: мы были почти прилежными учениками и сделали propTypes, для <Article />. Из ошибки сразу понятно: мы не передаем значение author (потому что, мы передаем name).

Во-вторых: мы все же накосячили, и забыли добавить в propTypes для <Article /> свойство id. Хорошо, что кода мало и ошибка сразу нашлась. Не забывайте про перечисление всех свойств в propTypes - это супер шпаргалка.

В-третьих: мы из <Add /> не передаем id и bigText.

Тем не менее, нельзя не отметить, что мы добавили динамики в наше приложение! Новость добавляется, причем счетчик новостей работает (а мы его вообще не трогали). Кто уже празднует - молодец, но кто хочет пофиксить все ошибки и сдать работу на пять - милости прошу в заключительный пункт основного курса.


Работа над ошибками

Я бы хотел, чтобы все это вы пофиксили сами. Но, чтобы не быть автором, который "оп-па, косяк, давайте-ка я отдам это вам на домашку", я все сделаю и опишу. Просто попробуйте. У вас получится. Практика решает. Обязательно всегда практикуйтесь для закрепления материала.

Задача:

  • добавить в форму добавления textarea для bigText;
  • передавать author;
  • передавать id (можно сделать через timestamp (отметку времени в ms): +new Date()). Для обучающего примера будет достаточно;
  • исправить propTypes в <News />

news added

Решение:

Полный код того, что находится в <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> 

Готово!

на данный момент.

Назад: Работа с формой
Дальше: Итоги по основам