Я использую 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>
</>
);
},
);
Если я правильно понял, у вас один код productItemRef для всех товаров. Таким образом, каждый productItem будет переопределять closeModals. Это означает, что ваш вызов из родительского компонента будет вызывать только методы последнего элемента.
@ДрюРиз. Я постараюсь создать его, когда вернусь. Если я попытаюсь вызвать его у родителя при нажатии кнопки, это сработает. Но он не открывается при использовании useEffect. Считаете ли вы, что добавление массива зависимостей сработает? Я попытался поместить console.info внутри closeModals в useImperativeHandle, и он записывает его, но setIsEditOpen не работает.
Вы можете просмотреть этот раздел документации, чтобы управлять несколькими ссылками. response.dev/learn/…
@Джозеф не уверен, каково ожидаемое поведение. Если у вас может быть открыто только одно модальное окно, то вы можете установить closeModals только после открытия одного, чтобы closeModals использовал методы текущего открытого. Если может быть открыто несколько диалоговых окон, вам следует либо использовать массив ссылок и передать N-ю ссылку N-му элементу, либо вы можете использовать одну ссылку, которая использует массив, и каждый элемент помещает в нее свой собственный closeModals.
@DrewReese, я это повторил. Пожалуйста, посмотрите этот stackblitz: stackblitz.com/edit/…. После нажатия сохранить работает только один пункт
@Габриэле Петриоли. Можно открыть только один диалог.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


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