# Связываем компоненты вместе

&#x20;В этом уроке мы должны ответить на несколько вопросов:

* Где хранить список?
* Каким образом мы добавляем новые пункты?
* Как отобразить несколько дел сразу?
* Каким образом мы их удаляем?

В этом уроке присутствует повышенная концентрация JavaScript. Я постараюсь объяснить все сложные моменты, а глубже с теорией мы познакомимся в [следующей главе](/react-js-for-dummies/teoriya-intensiv-po-javascript.md).

## Где хранить список?

У нас есть два компонента: первый (`Field.jsx`) добавляет новые элементы, второй (`Item.jsx`) отображает их и имеет возможность удалить. Так как компоненты не должны знать друг о друге, быть максимально изолированными, хранить данные мы будем в ближайшем общем предке, стало быть, `TodoApp.jsx`:

{% code title="TodoApp.jsx" %}

```jsx
export function TodoApp() {
  const [items, setItems] = useState([]);

...
```

{% endcode %}

## Каким образом мы добавляем новые пункты?

Нам нужен способ сообщить в главный компонент (`TodoApp`), что произошло нажатие кнопки добавления, и что `Field` хочет добавить новую запись в наш список. В Реакте это делается с помощью *коллбеков*. Коллбек (от англ. call back — "перезвони мне") — это функция, которая передается из родительского компонента в дочерний как параметр, которую дочерний компонент вызывает для того, чтобы сигнализировать родителю о том, что произошло какое-то действие. Давай посмотрим, как мы можем применить это в нашей ситуации.

Для начала добавим `Field` в `TodoApp`(не забудь импортировать его):

{% code title="TodoApp.jsx" %}

```jsx
import { Field } from './field';

export function TodoApp() {
  //...

  return (
    <div>
      <Field />
    </div>
  );
}
```

{% endcode %}

Создадим функцию, которая будет коллбеком и добавим ее к пропсам `Field`:

{% code title="TodoApp.jsx" %}

```jsx
export function TodoApp() {
  const [items, setItems] = useState([]);


  function addNewItem() {

  }

  return (
    <div>
      <Field onAdd={addNewItem} />
    </div>
  );
}
```

{% endcode %}

Перейдем в `Field.jsx` и пропишем этот коллбек как обработчик нажатия кнопки:

{% code title="Field.jsx" %}

```jsx
export function Field(props) { // не забудь добавить сюда props

  const [text, setText] = useState('');

  //...
  
  function onButtonClick() {
    props.onAdd(text)
  }

  return (
    //...
    <button onClick={onButtonClick}>
      New
    </button>
  );
}
```

{% endcode %}

(некоторые куски кода были пропущены для краткости)

Что здесь происходит:

1. При нажатии кнопки вызывается `onButtonClick`
2. `onButtonClick`  вызывает переданный "сверху" `onAdd` и передает ему как первый аргумент значение поля ввода.

Вернемся в `TodoApp.jsx` и напишем код, который добавляет элемент в список:

{% code title="TodoApp.jsx" %}

```jsx
function addNewItem(newItem) {
  setItems([...items, newItem]);
}
```

{% endcode %}

Это сложная конструкция, давай разберем по частям:

1. Функция `addNewItem` вызывается, когда мы нажимаем на кнопку добавления. `newItem` — новый элемент.
2. Внутри неё мы изменяем значение `items`, теперь оно равно`[...items, newItem]`
3. `[...items, newItem]` — это способ описать массив, который состоит из всех элементов `items`, а также `newItem` .

## Как отобразить несколько элементов списка сразу

Теперь, когда у нас есть список, в который мы можем добавлять элементы, нам надо научиться их отображать. Компонент элемента списка, кстати, должен выглядеть примерно так:

{% code title="Item.jsx" %}

```jsx
export function Item(props) {
  return (
    <div>
      <span>{props.name}</span>
      <button>Delete</button>
    </div>
  );
}
```

{% endcode %}

Вернемся в `TodoApp`. У нас есть массив `items`, надо "сконвертировать" их в `<Item name={...}/>` (кстати, не забудь импорировать его) и добавить в дерево. В этом нам поможет функция `map`.

> Представь, что у тебя есть фабрика с конвейером. Пустые бутылки проходят по конвейеру через аппарат, который наливает в них воду. На входе было 100 пустых бутылок, на выходе — 100 полных бутылок. Так вот твой массив — это бутылки на конвейере, а map — это аппарат.

```javascript
const array1 = [1,2,3,4]

function f(x) {
  return x * x
}

const array2 = array1.map(f)

// array2 = [1,4,9,16]
```

> `map` вызывает функцию `f` с каждым элементом массива, над которым она была вызвана, и составляет новый массив из результатов выполнения функции.

В нашем случае есть массив строк, нужно из него сделать массив `Item`:

{% code title="TodoApp.jsx" %}

```jsx
function createItem(name) {
  return <Item name={name} />;
}
```

{% endcode %}

Теперь добавим наши элементы в компонент `TodoApp`:

{% code title="TodoApp.jsx" %}

```jsx
return (
    <div className="todo">
      <Field onAdd={addNewItem} />
      {items.map(createItem)}
    </div>
  );
```

{% endcode %}

Проверь в браузере, что элементы добавляются.

### Дополнительная информация

React устроен таким образом, что он пытается произвести как можно меньше манипуляций с веб-страницей для достижения нужного результата. Недостатком этого метода является то, что он иногда "путает" элементы массива и не всегда знает, какой элемент документа соответствует какому элементу массива.

```javascript
// было: 
['привет', 'привет', 'мир']
// стало: 
['привет', 'мир']
// какой из элементов массива был удален: первый или второй?
```

Для этого было придумано "магическое" свойство `key`. Правило простое:

> Если у нас есть массив React-компонентов, то у каждого из них должен быть свой, уникальный внутри массива, `key`.

Давай разберемся, как им пользоваться.

Мы говорили о том, что `map` вызывает функцию `f` с каждым элементом массива. Однако, map передает в `f` не один, а три аргумента:

1. Элемент массива
2. Порядковый номер (*индекс*) элемента
3. Сам массив.

Перепишем функцию `createItem` так, чтобы она использовала порядковый номер в качестве `key`:

{% code title="TodoApp.jsx" %}

```jsx
function createItem(name, index) {
  return <Item name={name} key={index}/>;
}
```

{% endcode %}

## Удаление элементов

В этой главе мы познакомимся с сестрой функции `map` — функцией `filter`.

> `filter` вызывает функцию `f` с каждым элементом массива (а также его индексом и самим массивом), над которым она была вызвана, и составляет новый массив из тех элементов **исходного** массива, для которых `f` вернула `true`.

Пример: отфильтровать массив чисел таким образом, чтобы остались только положительные:

```jsx
const numbers = [0, 1, 2, 3, 4, -1, -2, -3, -4];

function isPositive(number) {
  return number > 0
}
const positive = numbers.filter(isPositive)
// positive = [1, 2, 3, 4]
```

Мы хотим написать функцию, которая обновляет список items, удалив из него элемент с указанным индексом (`removedIndex`):

{% code title="TodoApp.jsx" %}

```jsx
export function TodoApp() { 
  
  // ...
  
  function removeItem(removedIndex) {
  
    function f(item, index) {
      return removedIndex !== index;
    }
  
    setItems(items.filter(f))
  }
  
  //return ...
}
```

{% endcode %}

Что происходит: `filter` вызывает `f` c элементами массива, `f` в свою очередь, возвращает `true` для всех элементов, кроме `removedIndex`.

Мы теперь можем дать нашим `Item` возможность удалять себя:

{% code title="TodoApp.jsx" %}

```jsx
function createItem(name, index) {

  function remove() {
    removeItem(index)
  }

  return <Item name={name} key={index} onDelete={remove}/>;
}
```

{% endcode %}

А Item может запрашивать удаление себя по нажатию кнопки:

```jsx
export function Item(props) {
  return (
    <div>
      <span>{props.name}</span>
      <button onClick={props.onDelete}>
        Delete
      </button>
    </div>
  );
}
```

Проверь в браузере, что элементы добавляются и удаляются.

О, одна небольшая вещь для самостоятельной работы:&#x20;

1. Подумай, как сделать так, что поле ввода очищается после нажатия кнопки добавления. &#x20;
2. Подумай, как сделать так, чтобы нельзя было добавить пустые строки

Ответ — в конце следующей главы.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lesha.gitbook.io/react-js-for-dummies/app-todo/svyazyvaem-komponenty-vmeste.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
