Repository navigation
FormulaEditorPattern
Type: Pattern Domain: Frontend — Form / Configuration Applies to: React 19 + TypeScript + Carbon Design System + Monaco Editor Enforced by: react-specialist Last updated: 2026-09-10 Status: Current
The Formula Editor Integration pattern defines how to embed the FormulaInput component (Monaco-powered math formula editor with BigNumber evaluation) into configuration pages for authoring date-effective formula sets. It covers the complete data flow from API DTOs through TanStack Query hooks to the editor component, including carry-forward logic, validation error mapping, and read-only vs editable modes.
Without this pattern, developers would:
- Re-implement formula editor integration inconsistently across pages
- Miss the carry-forward logic for new formula sets
- Handle validation errors differently (or not at all)
- Confuse fixed vs dynamic parameters in
FormulaInput - Forget to wire the complete-replace PUT semantics
This pattern ensures every formula configuration page follows the same architecture: service → hooks → page → FormulaInput rows → backend validation → UI error display.
FormulaConfigurationDetailPage
├── useFormulaSetList (carry-forward reference data)
├── useCreateFormulaSet / useUpdateFormulaSet / useDeleteFormulaSet (mutations)
├── State: area, startDate, endDate, formulas[]
├── Computed: isEditable (future open-ended only)
└── FormulaRow[] (one per formula)
├── TextInput for formulaKey
├── FormulaInput (Monaco + mathjs BigNumber)
│ ├── fixedParams: {} (server-authoritative, not used here)
│ ├── dynamicParams: generated from canonical namespaces
│ ├── displayResult: true (live preview)
│ ├── displayDependencyGraph: true (variable usage)
│ └── onChange: updates local formula state
└── Validation errors from formula.validationErrors[]
Source:
src/pages/FormulaConfigurationDetail/index.tsx—FormulaConfigurationDetailPage+FormulaRow
// Page-level: mutations and state
const updateMutation = useUpdateFormulaSet();
const createMutation = useCreateFormulaSet();
const deleteMutation = useDeleteFormulaSet();
const [area, setArea] = useState<"INTERIOR" | "COASTAL">("INTERIOR");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [formulas, setFormulas] = useState<FormulaItemDto[]>([]);
// Carry-forward for new sets
useEffect(() => {
if (isNew && listData?.content?.length) {
const recentSets = listData.content.filter(
(s) => s.area === area && !s.deleted,
);
if (recentSets.length > 0) {
const mostRecent = recentSets[0];
setFormulas(
mostRecent.formulas.map((f, idx) => ({
...f,
sortOrder: idx + 1,
validationErrors: [], // Clear errors for carry-forward
})),
);
setStartDate(formatDate(new Date().toISOString()));
}
}
}, [area, isNew, listData]);
// Editable determination
const isEditable = useMemo(() => {
if (isNew) return true;
const today = new Date();
today.setHours(0, 0, 0, 0);
const start = new Date(startDate);
return endDate === "" && start > today;
}, [isNew, startDate, endDate]);
// Save: complete-replace PUT
const handleSave = async () => {
const dto = {
area,
startDate,
endDate: endDate || null,
formulas: formulas.map((f, idx) => ({ ...f, sortOrder: idx + 1 })),
};
if (isNew) await createMutation.mutateAsync(dto);
else await updateMutation.mutateAsync({ id: Number(formulaSetId), dto });
};
// Per-row FormulaInput integration
<FormulaInput
initialFormula={expression}
onChange={handleExpressionChange}
readOnly={false}
displayResult={true}
displayDependencyGraph={true}
ariaLabel={`Formula ${index + 1}: ${formulaKey || "unnamed"}`}
fixedParams={}
dynamicParams={generateDynamicParams(availableNamespaces)}
/>;
// Validation error display
{
formula.validationErrors.length > 0 && (
<div className="formula-row__errors" role="alert">
{formula.validationErrors.map((err, i) => (
<div key={i} className="formula-row__error">
<code>{err.code}</code>: {err.message}
</div>
))}
</div>
);
}// ❌ WRONG: Partial update via PATCH (API doesn't support it)
await updateFormulaSet(id, { formulas: [newFormula] }); // Missing area, startDate, endDate
// ❌ WRONG: Using fixedParams for user-editable variables
<FormulaInput
fixedParams={{ "da.mature.volume": 1000 }} // These are server-authoritative!
dynamicParams={}
/>;
// ❌ WRONG: Skipping carry-forward for new sets
const [formulas, setFormulas] = useState<FormulaItemDto[]>([]); // Empty, no reference data
// ❌ WRONG: Not clearing validationErrors on carry-forward
setFormulas(mostRecent.formulas); // Errors persist, confusing user
// ❌ WRONG: Allowing edit of closed/historical sets
const isEditable = !set.deleted; // Missing date check!
// ❌ WRONG: Not mapping backend validation errors to UI
// Backend returns 422 with validationErrors[] — must populate formula.validationErrors
// ❌ WRONG: Hardcoding namespaces in multiple places
const namespaces = ["da", "sc", "submission", "hbs", "fta"]; // Duplicated in generateDynamicParamsWhy this is wrong:
- Partial updates violate the complete-replace PUT contract
-
fixedParamsare for server-provided constants (e.g., from API), not user-editable variables - Carry-forward is a core UX requirement — new sets must start from the latest version
- Validation errors must be cleared on carry-forward or users see stale errors
- Editing closed sets breaks the date-effective lifecycle
- Backend validation errors must surface in the UI per formula row
- Canonical namespaces defined in one place (
generateDynamicParams)
- Creating any page that edits formula sets (or similar date-effective configuration with math expressions)
- Integrating
FormulaInputinto a form with multiple formula rows - Implementing carry-forward from a previous version
- Displaying backend validation errors inline with formula editors
- Simple forms with a single formula (use
FormulaInputdirectly without the row pattern) - Read-only display of formulas (use the read-only branch of
FormulaRow) - Non-formula configuration (different DTO structure, different validation)
-
Custom namespaces: Pass different
availableNamespacestoFormulaRowif the domain uses different canonical namespaces -
Additional fixed params: If the backend provides fixed parameters via API, merge them into
fixedParams(e.g., from a separate query) -
Different validation codes: The error display handles any
code/messagestructure fromFormulaValidationError - Deviations require ADR — this pattern is tied to the backend lifecycle contract
| Check | Method |
|---|---|
| Complete-replace PUT payload | Grep for updateFormulaSet.*dto — ensure area, startDate, endDate, formulas all present |
| Carry-forward logic | Grep for useFormulaSetList + isNew + setFormulas with validationErrors: []
|
| Validation error mapping | Grep for validationErrors in FormulaRow — must render err.code + err.message
|
| Editable determination | Grep for isEditable — must check endDate === '' AND startDate > today
|
| Fixed vs dynamic params | Grep for FormulaInput props — fixedParams should be {} or server data, dynamicParams from generateDynamicParams
|
- TanStack Query Mutations — Standard mutation pattern with invalidation
- Carbon Form Layout — PageTitle + Column + section structure
- Date-Effective Configuration — Lifecycle pattern for startDate/endDate sets
- Monaco Editor Integration — Base pattern for Monaco-based editors
- FormulaConfigurationDetail — Canonical implementation
- FormulaInput — Component API and props
- FormulaConfigurationService — Service and hooks
- FormulaConfigurationAPI — Backend API contract
- ADR: Date-Effective Formula Configuration Lifecycle (to be created)
This pattern page was generated from the Issue #1223 implementation. Consider proposing an agent-update to react-specialist to add this to its ANALYZE checklist for configuration pages with formula editors.
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository