Модальное окно не закрывается после ответа в React

Я использую useImperativeHandle и ref, чтобы предоставить родителю доступ к некоторым функциям. Мне интересно, почему модальное окно здесь не закрывается? Я попытался разместить alert и console.info, и это работает, но setIsEditOpen() и setIsDeleteOpen() не работают внутри closeModals.

СТЭКБЛИЦ

Продукт.tsx

export default function Products() {
    const { products } = useLoaderData<typeof loader>();
    const actionData = useActionData<typeof action>();
    const productItemRef = useRef<ModalRef>(null);

    useEffect(() => {
        if (actionData?.ok) {
            productItemRef?.current.closeModals();
        }
      }, [actionData]);

    return (
        <div className = "divide-y">
            {products.map((product) => (
                <ProductItem
                    key = {product.id}
                    product = {product}
                    ref = {productItemRef}
                    lastResult = {actionData}
                />
            ))}
        </div>
    )

Продуктитем.tsx

type ProductItemProps = {
    product: {
        id: string;
        name: string;
        description: string;
        price: number;
        category: string;
    };
    lastResult?: any;
};

export type ModalRef = {
    closeModals: () => void;
};

export const ProductItem = forwardRef(
    ({ product, lastResult }: ProductItemProps, ref: Ref<ModalRef>) => {
        const [isEditOpen, setIsEditOpen] = useState(false);
        const [isDeleteOpen, setIsDeleteOpen] = useState(false);
        const { name, description, price, category } = product;

        useImperativeHandle(ref, () => ({
            closeModals: () => {
                setIsEditOpen(false);
                setIsDeleteOpen(false);
            },
        }));

        return (
            <>
                <ReusableSheet
                    isOpen = {isEditOpen}
                    setIsOpen = {setIsEditOpen}
                    title = "Edit a product"
                >
                    <EditProductForm
                        setIsOpen = {setIsEditOpen}
                        product = {product}
                        lastResult = {lastResult}
                    />
                </ReusableSheet>
                <ReusableDialog
                    isOpen = {isDeleteOpen}
                    setIsOpen = {setIsDeleteOpen}
                    title = "Remove a product from the list"
                    description = {
                        <span>
                            Are you sure you want to remove{" "}
                            <span className = "font-bold">{name}</span>{" "}
                            from the list of products?
                        </span>
                    }
                    hideCloseButton
                >
                    <DeleteProductForm setIsOpen = {setIsDeleteOpen} product = {product} />
                </ReusableDialog>
            </>
        );
    },
);

Не то чтобы я думал, что это проблема, но похоже, что в вашем крючке useImperativeHandle отсутствует массив зависимостей. Попробуйте добавить один, т. е. useImperativeHandle(ref, () => ({ ... }), []);. В остальном то, что у вас есть, выглядит нормально. Можете ли вы создать работающую демо-версию песочницы?

Drew Reese 19.06.2024 08:45

Если я правильно понял, у вас один код productItemRef для всех товаров. Таким образом, каждый productItem будет переопределять closeModals. Это означает, что ваш вызов из родительского компонента будет вызывать только методы последнего элемента.

Gabriele Petrioli 19.06.2024 10:57

@ДрюРиз. Я постараюсь создать его, когда вернусь. Если я попытаюсь вызвать его у родителя при нажатии кнопки, это сработает. Но он не открывается при использовании useEffect. Считаете ли вы, что добавление массива зависимостей сработает? Я попытался поместить console.info внутри closeModals в useImperativeHandle, и он записывает его, но setIsEditOpen не работает.

Joseph 19.06.2024 11:59

Вы можете просмотреть этот раздел документации, чтобы управлять несколькими ссылками. response.dev/learn/…

Dorian Massoulier 19.06.2024 21:37

@Джозеф не уверен, каково ожидаемое поведение. Если у вас может быть открыто только одно модальное окно, то вы можете установить closeModals только после открытия одного, чтобы closeModals использовал методы текущего открытого. Если может быть открыто несколько диалоговых окон, вам следует либо использовать массив ссылок и передать N-ю ссылку N-му элементу, либо вы можете использовать одну ссылку, которая использует массив, и каждый элемент помещает в нее свой собственный closeModals.

Gabriele Petrioli 20.06.2024 08:55

@DrewReese, я это повторил. Пожалуйста, посмотрите этот stackblitz: stackblitz.com/edit/…. После нажатия сохранить работает только один пункт

Joseph 20.06.2024 09:58

@Габриэле Петриоли. Можно открыть только один диалог.

Joseph 20.06.2024 10:00
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
7
111
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема

Вы создаете и ссылаетесь только на одну ссылку React в файле продуктов и передаете ее каждому сопоставленному ProductItem из массива products. Это означает, что каждый <ProductItem ref = {productItemRef} .... /> перезаписывает значение ссылки, а последний сопоставленный элемент — это то, на что productItemRef имеет ссылку. Когда вы нажимаете кнопку «Сохранить», скажем, в модальном окне редактирования продукта «Банан», ссылка пытается вызвать closeModals модального окна редактирования продукта «Манго», а модальное окно «Банан» никогда не закрывается.

Предложение решения

Создание модальных ссылок

Как минимум вам нужно будет создать ссылку React для каждого сопоставленного ProductItem, чтобы на них можно было ссылаться индивидуально.

Пример:

// Array of React refs
const productItemRef = useRef<React.RefObject<ModalRef>[]>([]);

// Set modal refs to current value or create if necessary
productItemRef.current = products.map(
  (_, i) => productItemRef.current[i] ?? createRef()
);

...

return (
  <div className = "flex flex-col gap-2">
    {products.map((product, i) => (
      <ProductItem
        key = {product.id}
        product = {product}
        ref = {productItemRef.current[i]} // <-- pass modal ref
      />
    ))}
  </div>
);

Закрытие модальных окон

Наивный/тривиальный подход мог бы заключаться в том, чтобы просто перебрать весь массив модальных ссылок и вызвать closeModals для каждой из них.

Пример:

useEffect(() => {
  if (actionData?.type === 'success') {
    productItemRef.current.forEach((productRef) =>
      productRef.current?.closeModals()
    );
  }
}, [actionData]);

Улучшенным подходом было бы включение отредактированного идентификатора продукта в данные действия.

Пример:

EditProductForm — включите скрытое поле, которое передает product.Id в данные формы, доступное в обработчике действий.

export function EditProductForm({ setIsOpen, product }: EditProductFormProps) {
  const navigation = useNavigation();

  const isUpdating =
    navigation.formData?.get('intent') === 'edit-product' &&
    navigation.state === 'submitting';

  return (
    <Form method = "post">
      <div className = "flex justify-end gap-2 mt-2">

        {/* Hidden field to include with form data */}
        <input name = "productId" defaultValue = {product.id} hidden />

        {product.name}
        <Button
          type = "button"
          variant = "outline"
          disabled = {isUpdating}
          onClick = {() => setIsOpen(false)}
        >
          Cancel
        </Button>
        <Button
          name = "intent"
          value = "edit-product"
          disabled = {isUpdating}
          type = "submit"
        >
          {isUpdating ? (
            <Loader2 className = "mr-2 h-4 w-4 animate-spin" />
          ) : null}
          Save
        </Button>
      </div>
    </Form>
  );
}

Индексный файл продукта. Получите доступ к идентификатору продукта и верните ответ с данными JSON.

export const action = async ({ request, context }: ActionFunctionArgs) => {
  const formData = await request.formData();
  const productId = formData.get("productId"); // <-- get product id
  const intent = formData.get('intent');

  if (intent === 'edit-product') {
    return json({
      productId, // <-- return product id
      type: 'success',
    });
  }

  return null;
};

Закройте только открытое модальное окно редактирования продукта.

useEffect(() => {
  if (actionData?.type === 'success') {
    productItemRef.current[
      products.findIndex((product) => product.id === actionData?.productId)
    ].current?.closeModals();
  }
}, [actionData]);

Если я выберу последнюю часть, productItemRef.current[ products.findIndex((product) => product.id === actionData?.productId) ].current?.closeModals();, нужно ли мне изменить ссылку выше, например const productItemRef = useRef<React.RefObject<ModalRef>[]>([]);?

Joseph 20.06.2024 19:46

@Joseph Да, в любом случае вам нужен массив ссылок, чтобы ссылаться на открытые ссылки отдельных ProductItem компонентов.

Drew Reese 20.06.2024 19:48

Другие вопросы по теме