Странное поведение во время управления состоянием реакции hook, splice () удаляет неправильный элемент - PullRequest
0 голосов
/ 02 февраля 2020

У меня есть состояние, которое я использую для добавления новых элементов JSX:

    const [display, setDisplay] = useState<IDisplay>({
            BookingFormDropDown: [],
        }
    );

Добавление элемента onClick () с помощью этой функции из render ():

    const addNewService = () => {
        setDisplay(
            (prevState: IDisplay) => (
                {
                    ...prevState, BookingFormDropDown: [...prevState.BookingFormDropDown,
                        <div>
                            <BookingFormDropDown bookingData={bookingData}/>
                        </div>]
                }
            )
        );

    };

Циклы и извлечение эти элементы JSX в render () следующим образом:

                display.BookingFormDropDown.map((el: JSX.Element, i: number) => {
                    return <div key={i}>
                        <div>{el}</div>
                        <div className="row">
                            <div>
                                <button type="button"
                                        onClick={() => handelRemoveService(i)}>Remove
                                </button>
                            </div>
                        </div>
                    </div>
                })

Почему мое приложение всегда удаляет последний элемент при любом нажатии кнопки? Насколько я понимаю, после добавления нового элемента в массив приложение должно снова l oop display.BookingFormDropDown и новые значения индекса должны быть переданы для каждой кнопки, пока map ().

    const handelRemoveService = (i: number) => {
        const state: JSX.Element[] = display.BookingFormDropDown;
        state.splice(i, 1);
        setDisplay(
            (prevState: IDisplay) => ({
                ...prevState, BookingFormDropDown: state
            })
        );
    };

Только сегодня решил для реализации хуков компонент на основе классов прекрасно работает с аналогичным логином c.

Полный код компонента:

import React, {FunctionComponent, useState} from 'react';
import './BookingForm.css';
import Calendar from 'react-calendar';
import Slots from "../../components/Slots/Slots";
import ClientDetails from "../../components/ClientDetails";
import BookingFormDropDown from "../../components/BookingFormDropDrown";

//import {IService, IServiceCategory, IEmployee, ISlot} from "../../interfaces/IBookingFrom";

export interface IBookingData {
    Location: string[] //ILocation[]
    Categories: string[] //ICategory[]
    Services: string[] //IService[]
    Employees: string[] //IEmployee[]
    Slots: string[] //ISlot[]
    date: Date[] | Date
}

interface IDisplay {
    BookingFormDropDown: JSX.Element[]
    BookingStage: string | null
    Mode: string | null
    error: string | null
}

const BookingForm: FunctionComponent = () => {
    const [bookingData, setBookingData] = useState<IBookingData>({
        Location: ['t', 'l', 'r'],
        Categories: ['one', 'two', 'tree'],
        Services: ['one1', 'two1', 'tree1'],
        Employees: ['one2', 'two2', 'tree2'],
        Slots: ['Mon 12:00', 'Mon 14:01', 'Mon 15:02', 'Mon 12:03',
            'Mon 14:04', 'Mon 15:05', 'Mon 12:06', 'Mon 14:07', 'Mon 15:08',
            'Mon 15:09', 'Mon 12:10', 'Mon 14:11', 'Mon 15:12', 'Mon 12:13',
            'Mon 14:14', 'Mon 15:15'],
        date: new Date()
    });

    const [display, setDisplay] = useState<IDisplay>({
            BookingFormDropDown: [],
            BookingStage: null,
            Mode: null,
            error: null
        }
    );

    const handelRemoveService = (i: number) => {
        const state: JSX.Element[] = display.BookingFormDropDown;
        state.splice(i, 1);
        setDisplay(
            (prevState: IDisplay) => ({
                ...prevState, BookingFormDropDown: state
            })
        );
    };


    const addNewService = () => {
        setDisplay(
            (prevState: IDisplay) => (
                {
                    ...prevState, BookingFormDropDown: [...prevState.BookingFormDropDown,
                        <div>
                            <BookingFormDropDown bookingData={bookingData}/>
                        </div>]
                }
            )
        );

    };

    const handleNewDate = (newDate: Date[] | Date) => setBookingData({
        ...bookingData, date: newDate

    });

    return (
        <div className="container">
            <div className="row">
                <h1 className="col">Demo place</h1>
            </div>

            <BookingFormDropDown bookingData={bookingData}/>

            {/* Displaying  additional services from array*/
                display.BookingFormDropDown.map((el: JSX.Element, i: number) => {
                    return <div key={i}>
                        <div>{el}</div>
                        <div className="row">
                            <div className="col mb-4 d-flex justify-content-end">
                                <button type="button" className="col-md-2 btn btn-light"
                                        onClick={() => handelRemoveService(i)}>Remove
                                </button>
                            </div>
                        </div>
                    </div>
                })
            }

            <div className="row">
                <div className="col mb-4 d-flex justify-content-start">
                    <button type="button" className="col-md-2 btn btn-light"
                            onClick={() => addNewService()}>Add service
                    </button>
                </div>
            </div>

            <div className="row">
                <div className="col-md-4">
                    <Calendar minDetail={'year'}
                              onChange={handleNewDate}
                              value={bookingData.date}/>
                </div>

                <Slots list={bookingData.Slots}/>
            </div>

            <div className="row">
                <div className="col d-flex justify-content-center p-5">
                    <button type="button" className=" col-md-3 btn btn-light">NEXT</button>
                </div>
            </div>

            <ClientDetails/>
        </div>
    );
};

export default BookingForm;

Ответы [ 2 ]

1 голос
/ 03 февраля 2020

Вы уверены, что он удаляет последний элемент? Я вижу, что все ваши элементы созданы с одинаковыми данными, можете ли вы подтвердить, что действительно последний элемент удален? (так как index - это ваш ключ, он всегда будет выглядеть так, как будто последний элемент удален, так как length теперь: prevLength -1). Вот фиктивный пример, который показывает, что с вашим кодом все в порядке: https://codesandbox.io/s/bold-pond-me4si

Также просто совет:

   const handelRemoveService = (i: number) => {
        const state: JSX.Element[] = display.BookingFormDropDown;
        state.splice(i, 1);
        setDisplay(
            (prevState: IDisplay) => ({
                ...prevState, BookingFormDropDown: state
            })
        );
    };

вы изменяете оригинальный BookingFormDropDown (если у вас был какой-то код, который зависел от того, что у вас будут проблемы). При изменении состояния всегда желательно создать новую ссылку:

const state: JSX.Element[] = [...display.BookingFormDropDown];
1 голос
/ 02 февраля 2020

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

Еще одна вещь, не связанная с вашим вопросом: не переводите элементы JSX в состояние. Только необработанные сериализуемые данные принадлежат государству. Вы должны создавать JSX только во время рендеринга


, добавленного на основе комментариев, поэтому хранение jsx в состоянии не является хорошей идеей:

  1. это не реактивно. В вашем примере вы создаете BookingFormDropDown, вызывая функцию addNewService, и эта функция BookingFormDropDown будет зависать в этом состоянии навсегда. Он никогда не будет обновляться на основании чего-либо. Вы визуализируете его один раз и повторно используете результат этого рендера навсегда. bookingData prop теперь бесполезен, так как его изменения не будут отражены
  2. вы потеряете данные. Если, например, у вас в этом состоянии были разные типы BookingFormDropDown, вы не смогли бы, например, сосчитать их по типу, отфильтровать или изменить порядок. В вашем случае вы даже не можете извлечь правильные уникальные ключи из ваших элементов, потому что вы не знаете, что это за элементы. Теперь это черные ящики, все, что у вас есть, это их индексы, которые вызывают проблему, описанную в вашем вопросе. Делать что-либо на основе созданных выпадающих меню теперь невозможно
  3. Вы не можете должным образом проверить свое состояние в devtools или сериализовать его для сохранения в localalstorage
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...