Сперва приберемся:
Удалим лишние console.log'и, удалим обработчик handleCounter и параграф, который выводил количество кликов.
Затем создадим компонент - <TestInput />
, который будет просто отрисовывать (render) input перед списком новостей.
... // --- добавили test input --- class TestInput extends React.Component { render() { return ( <input className='test-input' value='введите значение' /> ) } } const App = () => { return ( <React.Fragment> <h3>Новости</h3> <TestInput /> {/* добавили вывод компонента */} <News data={myNews}/> </React.Fragment> ) } ...
Напомню про комментарии:
Первый комментарий, добавлен с помощью //
, так как данный комментарий не находится внутри JSX. А второй - находится, следовательно имеет вид {/* комментарий */}
. То есть, JSX - это не весь код вашего сценария, а только те части, где мы миксуем верстку и js (обычно в return методе у render'a).
Вообще, код сейчас не работает (но это не из-за комментария). Давайте посмотрим на ошибку внимательно:
Вы предоставили свойство value для поля, у которого нет onChange обработчика. Поэтому отрисовано поле только для чтения. Если поле должно быть изменяемо, используйте defaultValue. Либо установите onChange или readOnly. Проверьте render метод компонента TestInput.
Не могу не любить react за такие подробные сообщения об ошибках.
А вы кстати попробуйте сейчас изменить значение инпута. Ничего не выйдет. Здесь у нас есть два пути, и первый нам известный - использовать какое-нибудь свойство state в качестве динамически изменяемого значения инпута.
Для вызова setState, будем использовать событие onChange. Работа с ним не отличается от работы с onClick или другими любыми событиями. Главное - передать функцию-обработчик.
Не торопитесь, давайте подумаем еще раз:
Сможете сделать сами? Если да - отлично, если нет - решение ниже.
Подсказка #1: так может выглядеть состояние + функция-обработчик
... state = { myValue: '' } ... onChangeHandler = (e) => { this.setState({ myValue: e.target.value }) }, ...
Решение:
class TestInput extends React.Component { state = { myValue: '', } // используется e.currentTarget.value onChangeHandler = (e) => { this.setState({ myValue: e.currentTarget.value }) } render() { return ( <input className='test-input' onChange={this.onChangeHandler} value={this.state.myValue} placeholder='введите значение' /> ) } }
У нас есть placeholder - "введите значение", который будет показываться в момент загрузки страницы, так как наше начальное состояние input'a - пустая строка. При изменении, мы устанавливаем в переменную myValue - то что введено в input. Следовательно - input корректно изменяется.
Документация, что за свойство (MDN)
Представьте ситуацию: у вас будет onClick стоять на div
, внутри которого будет текст в параграфе. В итоге, при клике на текст в паграфе:
Кто вообще не понял о чем идет речь - нужно читать или весь раздел целиком (Кантор).
Обычно, мы хотим по клику отправлять значения инпута...
Задача: По клику на кнопку - показывать alert с текстом инпута.
Попробуйте сами.
Подсказка #1:
Вам необходимо:
<TestInput />
;this.state.myValue
;Подсказка #2:
Так как нам необходимо рендерить больше одного элемента, нужно обернуть их в родительский элемент, например в <div></div>
или React.Fragment
Решение:
class TestInput extends React.Component { state = { myValue: '', } onChangeHandler = (e) => { this.setState({ myValue: e.currentTarget.value }) } onBtnClickHandler = (e) => { alert(this.state.myValue); } render() { return ( <React.Fragment> <input className='test-input' onChange={this.onChangeHandler} value={this.state.myValue} placeholder='введите значение' /> <button onClick={this.onBtnClickHandler}>Показать alert</button> </React.Fragment> ) } }
Предлагаю добавить отступы для .test-input:
css/app.css
... .test-input { margin: 0 5px 5px 0; } ...
После добавления отступа в данном коде ничего не раздражает. Или нет? Как думаете, что здесь может расстроить борца за оптимизацию?
Ответ: каждый раз, после любого изменения у нас вызывается setState, а значит - полная перерисовка компонента. Не очень приятно. Опять же, чуть больше логики в момент render'a компонента и в пору будет расстроиться от "отзывчивого" поля ввода.
У нас логики в render-методе никакой нет, поэтому для нас это лишняя оптимизация. Однако, рассмотреть способ создания "неконтролируемых компонентов" нужно обязательно.
Документация по react, предлагает вам использовать в большинстве случаев контролируемые компоненты.
Главное отличие компонента от в том, что у него нет обработчика изменений, а значит нет постоянных вызовов setState и перерисовок.
Для того чтобы считать значение неконтролируемого компонента используется механизм refs.
Для неконтролируемого компонента в момент начальной загрузки можно указывать defaultValue.
Начнем по порядку:
defaultValue=''
) вместо valuethis.input = React.createRef();
this.input
(который в конструкторе создали)class TestInput extends React.Component { constructor(props) { super(props) this.input = React.createRef() } onBtnClickHandler = (e) => { // эта запись сейчас не работает alert(this.state.myValue); } render() { return ( <React.Fragment> <input className='test-input' defaultValue='' placeholder='введите значение' ref={this.input} /> <button onClick={this.onBtnClickHandler}>Показать alert</button> </React.Fragment> ) } }
Обновите страницу, попробуйте ввести значение. Работает? Работает!
Теперь нам нужно, научиться считывать значение: перепишите onBtnClickHandler следующим образом:
onBtnClickHandler = () => { alert(this.input.current.value) },
В так же есть пример использования refs и доступа к файлу, загружаемому через <input type="file" />
.
За этот урок, мы научились с вами не вызывать дорогой setState и render на "каждый чих".
P.S. конечно, в данном случае никакого выигрыша в производительности нет. Оба подхода хорошо сработают.
Вариант с контролируемыми и неконтролируемыми компонентами, работа с defaultValue и state являются одинаковыми для всех элементов форм.
Очень рекомендую посмотреть страницу документации на англ.языке по
на данный момент (включая alert и console.log). Пока что оставлен input с ref.