Submitting
When the user clicks Submit, Updog hands you the edited dataset grouped by source. You decide what to do with it — INSERT, UPDATE, DELETE, skip — by filtering on per-row flags.
onComplete
Section titled “onComplete”| Type | (result: DataEditorResult<TRow>) => void | Promise<void> |
DataEditorResult
Section titled “DataEditorResult”type DataEditorResult<TRow> = { sources: DataEditorSourceResult<TRow>[]; counts: { new: number; changed: number; deleted: number; invalid: number; }; learnedSynonyms: LearnedSynonyms;};
type LearnedSynonym = { source: string; // the imported header or cell value target: string; // the canonical it was mapped to (column title or option)};
type LearnedSynonyms = { columns: LearnedSynonym[]; // header → column matches values: LearnedSynonym[]; // cell value → option matches};counts is an aggregate across every source.
learnedSynonyms holds the aliases users confirmed during import that weren’t already in your synonyms, split into columns and values. Each entry is one 1:1 mapping, ready to store as a row. Both lists are [] when nothing new was learned. Persist them and feed them back through synonyms next time so repeat imports auto-match. See Remembering matches across imports.
DataEditorSourceResult
Section titled “DataEditorSourceResult”type DataEditorSourceResult<TRow> = { sourceId: string; sourceName: string; rows: ResultRow<TRow>[];};One entry per source. Pristine backend rows — unchanged, undeleted, not imported — are omitted. They’re no-ops.
ResultRow
Section titled “ResultRow”type ResultRow<TRow> = { row: TRow; isNew: boolean; isChanged: boolean; isDeleted: boolean; isValid: boolean;};The flags are orthogonal. A single row can be new + changed + deleted at once.
Updog doesn’t pre-group rows because routing depends on your backend — some clients upsert, some reject invalid rows, some keep deletions for audit.
Example
Section titled “Example”onComplete={async (result) => { for (const source of result.sources) { const inserts = source.rows.filter(r => r.isNew && !r.isDeleted && r.isValid); const updates = source.rows.filter(r => !r.isNew && r.isChanged && !r.isDeleted && r.isValid); const deletes = source.rows.filter(r => r.isDeleted && !r.isNew);
await persist(source.sourceId, { inserts, updates, deletes }); }}}If you never tagged sources via loadData, you’ll get one entry with sourceId: "backend".