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