Задать вопрос
Kentavr16
@Kentavr16
long cold winter

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

Затык с пониманием организации течения данных в реакт. Стоит задача - есть массив в стейте, пусть будет
const[arr,setArr]=useState([1,2,3])
. Исходя из данных в этом массиве нужно (к примеру) отрендерить несколько отдельных таймеров. используем мап -
arr.map(el=><TimerComponent name={el}/>)

Получаем условные таймеры с номерами 1,2,3. Вопрос - можно ли добавить еще один таймер к уже существующим, не вызывая перерендера ВСЕХ TimerComponent? Если просто запушить в исходный массив еще одно значение, это вызовет ререндер существующих таймеров, которые попросту запустятся заново. Возможно нужно по другому организовать хранение данных в приложении?
Извиняюсь за отсутствие полного примера исходного кода - слишком запутанный.

UPD: таки придется дополнить с полным кодом)

компонент App
import { useState } from 'react';
import './App.css';
import ControlPanel from './components/ControlPanel/ControlPanel';
import CreateTimer from './components/CreateTimer/CreateTimer';
import Timers from './components/Timer/Timers';
function App() {
//массив с данными таймеров
  const [data, setData] = useState([])
// функция с помощью которой обновляю массив с даными из дочернего компонента //CreateTimer
  function updateState(newdata) {
    setData(pr => [...pr, newdata])
  }
  return (
    <div className="App">
      <ControlPanel />
//компонент для добавления таймера
      <CreateTimer
        updateState={updateState}
      />
//компонент для отрисовки таймеров
      <Timers
        data={data}
      />
    </div>
  );
}

export default App;


Компонент CreateTimer

import React, { useState } from "react";
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';


export default function CreateTimer(props) {
//значения инпутов
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");

//создаю управляемые инпуты
    function titleHandler(e) {
        setTitle(e.target.value)
    }
    function descriptionHandler(e) {
        setDescription(e.target.value)
    }
    return (
        <Form style={{ width: "100vh", margin: "10px auto" }}>
            <Form.Group className="mb-3" controlId="formBasicEmail">
                <Form.Label>Название таймера</Form.Label>
                <Form.Control type="text"
                    placeholder="Название"
                    onChange={(e) => titleHandler(e)}
                    value={title} />
                <Form.Text className="text-muted">
                    Название не более 4-5 слов.
                </Form.Text>
            </Form.Group>

            <Form.Group className="mb-3" controlId="formBasicPassword">
                <Form.Label>Описание таймера</Form.Label>
                <Form.Control type="text"
                    placeholder="Описание таймера"
                    onChange={(e) => descriptionHandler(e)}
                    value={description}
                />
            </Form.Group>

            <Button variant="primary"
                onClick={() => {

//добавляю таймер путем обновления стейта компонента App

                    let newTimer = {
                        name: title,
                        description: description,
                        time: 0
                    }
                    props.updateState(newTimer);
                    setTitle("")
                    setDescription("")
                }}
            >
                Создать
            </Button>
            <hr />
        </Form>
    )
}


компонент Timers

import React, { useEffect, useState } from "react";
import Table from 'react-bootstrap/Table';

export default function Timers(props) {

//Непосредственно компонент для отрисовки таймера
    function Timer(props) {
        const [time, setTime] = useState(0)
        const [active, setActive] = useState(false)

        useEffect(() => {
            if (active) {
                let myTimer = setInterval(() => {
                    setTime(time + 1)
                }, 1000);
            }
        })
        return (
            <tr>
                <td>{props.index}</td>
                <td>{props.name}</td>
                <td>{props.description}</td>
                <td>{time}<button
                    onClick={() => {
                        setActive(!active)
                    }}
                >start</button></td>
            </tr>
        )
    }
    return (
        <Table striped bordered hover>
            <thead>
                <tr>
                    <th>Номер</th>
                    <th>Название</th>
                    <th>Описание</th>
                    <th>Время</th>
                </tr>
            </thead>
            <tbody>
//здесь отправляю компонент в рендер в таблицу
                {props.data.map((el, index) => < Timer
                    key={el.name}
                    index={index + 1}
                    name={el.name}
                    description={el.description}
                    time={el.time} />)}
            </tbody>
        </Table>
    )
}


При добавлении нового таймера через CreateTimer идущие таймеры сбрасываются.
Спасибо за любые подсказки.
  • Вопрос задан
  • 124 просмотра
Подписаться 1 Средний Комментировать
Помогут разобраться в теме Все курсы
  • Яндекс Практикум
    Мидл фронтенд-разработчик
    5 месяцев
    Далее
  • Яндекс Практикум
    React-разработчик
    3 месяца
    Далее
  • Яндекс Практикум
    Фронтенд-разработчик
    10 месяцев
    Далее
Решения вопроса 1
Надо давать элементам списка уникальный ключ – атрибут key.
Так React поймёт, какие элементы удалены, добавлены.

Индекс массива не подходит для значения key. Нужно что-то уникальное, характерное для каждого элемента массива, что не изменится при изменении порядка, скажем.

В примере в вопросе, видимо, уникальны сами значения 1, 2, 3 – можно их использовать для атрибута key:
arr.map(el => <TimerComponent name={el} key={el} />)
Ну и массивы в стейте, помните, надо не мутировать, а заменять массив целиком на новый.

Чтобы не перерендерился каждый раз вложенный компонент, можно его «мемоизировать»: обернуть в React.memo(), снабдив функцией, которая сравнивает предыдущее состояние и новое, и делает вывод, надо ли перерендерить. Тут, например, не надо перерендерить, если N остался тот же. Упрощённый Codepen:
для сравнения в коде App закомментируйте-раскомментируйте соседние строки, чтобы вместо ItemMemo работал просто Item
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы
ITK academy Нижний Новгород
от 80 000 до 120 000 ₽
ITK academy Воронеж
от 50 000 до 90 000 ₽