Skip to content

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.

Type(result: DataEditorResult<TRow>) => void | Promise<void>
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.

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.

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.

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".