> For the complete documentation index, see [llms.txt](https://lesha.gitbook.io/react-js-for-dummies/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lesha.gitbook.io/react-js-for-dummies/app-todo/svyazyvaem-komponenty-vmeste.md).

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

&#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. Подумай, как сделать так, чтобы нельзя было добавить пустые строки

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