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 идущие таймеры сбрасываются.
Спасибо за любые подсказки.
  • Вопрос задан
  • 77 просмотров
Решения вопроса 1
Надо давать элементам списка уникальный ключ – атрибут key.
Так React поймёт, какие элементы удалены, добавлены.

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

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

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

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

Войти через центр авторизации
Похожие вопросы