Main Pages — Add Modal Structure & Pattern¶
Part of: Main Pages guide See also: Add Modal Fields | Delete Modal
The Add modal (src/components/modals/<EntityModals>/Add<Entity>Modal.tsx) is built using ModalWithFormLayout.
Standard Props Interface¶
All Add modals use a consistent props interface with all props required (no optionals):
interface PropsToAddModal {
isOpen: boolean;
onClose: () => void;
title: string;
onRefresh: () => void;
}
Prop |
Type |
Description |
|---|---|---|
|
|
Controls modal visibility |
|
|
Callback to close the modal |
|
|
Modal title displayed in the header |
|
|
Callback to refresh the parent table after successful addition |
Usage in the parent page:
<AddEntityModal
isOpen={showAddModal}
onClose={() => setShowAddModal(false)}
title="Add entity"
onRefresh={refreshData}
/>
Code Organization¶
The Add modal follows this structure:
Imports — PatternFly components, layout components, RPC hooks, Redux, alerts
Props interface — Standard
PropsToAddModalwith four required propsComponent body:
API mutation hook
State declarations (spinner, form fields)
clearFields()— resets all form fieldsonAdd()— the add handler (API call, success/error alerts, refresh, close)cleanAndCloseModal()— clears fields and closes modalfieldsarray — form field definitionsmodalActionsarray — Add and Cancel buttons
Return —
ModalWithFormLayoutcomponent
Error Handling¶
Use alerts for error handling — do not use a separate ErrorModal component:
if (error) {
dispatch(
addAlert({
name: "add-entity-error",
title: error.message,
variant: "danger",
})
);
}
When an error occurs: dispatch a danger alert, reset the spinner, keep the modal open.
Compact Template¶
import React from "react";
import { Button, TextArea } from "@patternfly/react-core";
import ModalWithFormLayout, { Field } from "src/components/layouts/ModalWithFormLayout";
import InputRequiredText from "src/components/layouts/InputRequiredText";
import { useAddEntityMutation } from "src/services/rpcEntity";
import { useAppDispatch } from "src/store/hooks";
import { addAlert } from "src/store/Global/alerts-slice";
import { SerializedError } from "@reduxjs/toolkit";
interface PropsToAddModal {
isOpen: boolean;
onClose: () => void;
title: string;
onRefresh: () => void;
}
const AddEntityModal = (props: PropsToAddModal) => {
const dispatch = useAppDispatch();
const [addEntity] = useAddEntityMutation();
const [isAddButtonSpinning, setIsAddButtonSpinning] = React.useState(false);
const [entityName, setEntityName] = React.useState("");
const [description, setDescription] = React.useState("");
const clearFields = () => {
setEntityName("");
setDescription("");
};
const onAdd = () => {
setIsAddButtonSpinning(true);
addEntity({ cn: entityName, description: description || undefined }).then((response) => {
if ("data" in response) {
const error = response.data?.error as SerializedError;
if (error) {
dispatch(addAlert({ name: "add-entity-error", title: error.message, variant: "danger" }));
}
if (response.data?.result) {
dispatch(addAlert({ name: "add-entity-success", title: "New entity added", variant: "success" }));
clearFields();
props.onRefresh();
props.onClose();
}
}
setIsAddButtonSpinning(false);
});
};
const cleanAndCloseModal = () => { clearFields(); props.onClose(); };
const fields: Field[] = [
{
id: "modal-form-entity-name",
name: "Entity name",
pfComponent: (
<InputRequiredText dataCy="modal-textbox-entity-name" id="modal-form-entity-name"
name="cn" value={entityName} onChange={setEntityName} requiredHelperText="Required value" />
),
fieldRequired: true,
},
{
id: "modal-form-description",
name: "Description",
pfComponent: (
<TextArea data-cy="modal-textbox-description" id="modal-form-description" name="description"
value={description} onChange={(_e, v) => setDescription(v)} autoResize />
),
},
];
const modalActions: JSX.Element[] = [
<Button data-cy="modal-button-add" key="add-new" isDisabled={isAddButtonSpinning || entityName === ""}
form="add-modal-form" type="submit">Add</Button>,
<Button data-cy="modal-button-cancel" key="cancel-new" variant="link" onClick={cleanAndCloseModal}>Cancel</Button>,
];
return (
<ModalWithFormLayout dataCy="add-entity-modal" variantType="small" modalPosition="top" offPosition="76px"
title={props.title} formId="add-modal-form" fields={fields} show={props.isOpen}
onSubmit={() => onAdd()} onClose={cleanAndCloseModal} actions={modalActions} />
);
};
export default AddEntityModal;
Reference Implementations¶
Entity |
Modal file |
Notes |
|---|---|---|
Roles |
|
Simple two-field modal |
Certificate Mapping |
|
Clean pattern with alerts |
DNS Zones |
|
Uses custom Modal component |