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

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

  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
}

Проверим:

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

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

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

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

Задача:

  • добавить в форму добавления textarea для bigText;

  • передавать author;

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

  • исправить propTypes в <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>

Готово!

Исходный код на данный момент.

Last updated