Submission Flow
Learn how to validate, prepare, and submit documents to the MIMS API with proper error handling and user feedback.
Submission Overview
The submission flow consists of four phases:
- Validation - Check all required fields are completed
- Payload Generation - Build the submission payload
- API Request - Send the payload to the server
- Response Handling - Process success or handle errors
Complete Submission Implementation
1
Validate Fields
Before submission, validate all fields meet requirements:
hooks/useSubmission.tstsx
import { useDocumentStore, useSDK } from '@mims/sdk-react';
import { useState, useCallback } from 'react';
export function useSubmission() {
const { apiRequest } = useSDK();
const validateFields = useDocumentStore((s) => s.validateFields);
const getSubmissionPayload = useDocumentStore((s) => s.getSubmissionPayload);
const fields = useDocumentStore((s) => s.fields);
const [status, setStatus] = useState<'idle' | 'validating' | 'submitting' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
const [submissionId, setSubmissionId] = useState<string | null>(null);
const getValidationSummary = useCallback(() => {
const allFields = Array.from(fields.values());
const requiredFields = allFields.filter(f => f.definition.required);
const completedRequired = requiredFields.filter(f => f.value !== null);
const fieldsWithErrors = allFields.filter(f => f.errors.length > 0);
return {
totalFields: allFields.length,
requiredFields: requiredFields.length,
completedRequired: completedRequired.length,
fieldsWithErrors: fieldsWithErrors.length,
isComplete: completedRequired.length === requiredFields.length,
progress: requiredFields.length > 0
? (completedRequired.length / requiredFields.length) * 100
: 100,
};
}, [fields]);
return { status, error, submissionId, getValidationSummary };
}2
Build Submission Payload
The SDK provides a method to generate the submission payload:
Payload Structuretsx
// Generated by getSubmissionPayload()
interface DocumentSubmissionPayload {
documentId: string;
submittedAt: string; // ISO 8601 timestamp
renderContext: {
scale: number;
viewportWidth: number;
viewportHeight: number;
devicePixelRatio: number;
};
pages: Array<{
pageNumber: number;
width: number;
height: number;
rotation: number;
}>;
fields: Array<{
id: string;
type: FieldType;
page: number;
position: { x: number; y: number; width: number; height: number };
value: AnyFieldValue;
style?: { fontSize?: number; fontFamily?: string; textAlign?: string };
}>;
checksum: string; // Integrity verification hash
}3
Submit to API
Send the payload using the authenticated API request:
Submit Functiontsx
const submit = useCallback(async () => {
setStatus('validating');
setError(null);
// Run validation
const isValid = validateFields();
if (!isValid) {
setStatus('error');
setError('Please complete all required fields');
return { success: false, error: 'Validation failed' };
}
// Generate payload
const payload = getSubmissionPayload();
if (!payload) {
setStatus('error');
setError('No document loaded');
return { success: false, error: 'No document' };
}
setStatus('submitting');
try {
const result = await apiRequest<SubmissionResponse>('/sdk/documents/submit', {
method: 'POST',
body: JSON.stringify(payload),
});
setStatus('success');
setSubmissionId(result.submissionId);
return { success: true, submissionId: result.submissionId };
} catch (err) {
const message = err instanceof Error ? err.message : 'Submission failed';
setStatus('error');
setError(message);
return { success: false, error: message };
}
}, [validateFields, getSubmissionPayload, apiRequest]);4
Handle Response
Process the server response and update UI accordingly:
Response Handlingtsx
interface SubmissionResponse {
success: boolean;
submissionId: string;
documentId: string;
timestamp: string;
message?: string;
}
// In your component
function SubmitButton({ onSuccess }: { onSuccess?: (id: string) => void }) {
const { submit, status, error, submissionId } = useSubmission();
const handleClick = async () => {
const result = await submit();
if (result.success && result.submissionId) {
onSuccess?.(result.submissionId);
}
};
return (
<div>
<button
onClick={handleClick}
disabled={status === 'validating' || status === 'submitting'}
className="px-4 py-2 bg-emerald-500 text-white rounded disabled:opacity-50"
>
{status === 'validating' && 'Validating...'}
{status === 'submitting' && 'Submitting...'}
{status === 'idle' && 'Submit Document'}
{status === 'success' && 'Submitted!'}
{status === 'error' && 'Try Again'}
</button>
{error && (
<p className="text-red-500 text-sm mt-2">{error}</p>
)}
{status === 'success' && (
<p className="text-emerald-500 text-sm mt-2">
Submission ID: {submissionId}
</p>
)}
</div>
);
}Pre-Submission Validation UI
Show users which fields need attention before they can submit:
ValidationSummary.tsxtsx
function ValidationSummary() {
const fields = useDocumentStore((s) => s.fields);
const goToPage = useDocumentStore((s) => s.goToPage);
const selectField = useDocumentStore((s) => s.selectField);
const incompleteRequired = Array.from(fields.values())
.filter(f => f.definition.required && !f.value);
if (incompleteRequired.length === 0) {
return (
<div className="p-4 bg-emerald-50 border border-emerald-200 rounded-lg">
<p className="text-emerald-700 font-medium">
All required fields are complete. Ready to submit!
</p>
</div>
);
}
return (
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<p className="text-amber-700 font-medium mb-3">
{incompleteRequired.length} required field(s) need attention:
</p>
<ul className="space-y-2">
{incompleteRequired.map((field) => (
<li key={field.definition.id}>
<button
onClick={() => {
goToPage(field.definition.page);
selectField(field.definition.id);
}}
className="text-amber-600 hover:text-amber-800 underline text-sm"
>
{field.definition.label || field.definition.id} (Page {field.definition.page})
</button>
</li>
))}
</ul>
</div>
);
}Retry Logic
Implement automatic retry for transient failures:
Retry Implementationtsx
async function submitWithRetry(
payload: DocumentSubmissionPayload,
apiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>,
maxRetries = 3
): Promise<SubmissionResponse> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await apiRequest<SubmissionResponse>('/sdk/documents/submit', {
method: 'POST',
body: JSON.stringify(payload),
});
} catch (error) {
lastError = error instanceof Error ? error : new Error('Unknown error');
// Don't retry on validation errors (4xx)
if (lastError.message.includes('400') || lastError.message.includes('422')) {
throw lastError;
}
// Wait before retrying (exponential backoff)
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError ?? new Error('Max retries exceeded');
}Submission Idempotency
The submission endpoint is idempotent based on the checksum. If you retry with the same payload, you'll receive the original submission ID rather than creating a duplicate.
Offline Support
Queue submissions when offline and retry when connectivity returns:
Offline Queuetsx
// Simple offline queue using localStorage
const QUEUE_KEY = 'mims_submission_queue';
function queueSubmission(payload: DocumentSubmissionPayload) {
const queue = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]');
queue.push({
payload,
queuedAt: new Date().toISOString(),
attempts: 0,
});
localStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
}
async function processQueue(apiRequest: ApiRequestFn) {
const queue = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]');
const remaining = [];
for (const item of queue) {
try {
await apiRequest('/sdk/documents/submit', {
method: 'POST',
body: JSON.stringify(item.payload),
});
} catch (error) {
item.attempts++;
if (item.attempts < 5) {
remaining.push(item);
}
}
}
localStorage.setItem(QUEUE_KEY, JSON.stringify(remaining));
}
// Listen for online event
if (typeof window !== 'undefined') {
window.addEventListener('online', () => {
processQueue(apiRequest);
});
}Success Confirmation
Show a confirmation screen after successful submission:
SubmissionConfirmation.tsxtsx
interface SubmissionConfirmationProps {
submissionId: string;
documentId: string;
onNewDocument?: () => void;
onViewSubmission?: (id: string) => void;
}
function SubmissionConfirmation({
submissionId,
documentId,
onNewDocument,
onViewSubmission,
}: SubmissionConfirmationProps) {
return (
<div className="max-w-md mx-auto text-center py-12">
<div className="w-16 h-16 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-6">
<CheckIcon className="w-8 h-8 text-emerald-600" />
</div>
<h2 className="text-2xl font-bold mb-2">Document Submitted!</h2>
<p className="text-neutral-600 mb-6">
Your document has been successfully submitted and is being processed.
</p>
<div className="bg-neutral-50 rounded-lg p-4 mb-6 text-left">
<dl className="space-y-2 text-sm">
<div className="flex justify-between">
<dt className="text-neutral-500">Submission ID</dt>
<dd className="font-mono">{submissionId}</dd>
</div>
<div className="flex justify-between">
<dt className="text-neutral-500">Document ID</dt>
<dd className="font-mono">{documentId}</dd>
</div>
<div className="flex justify-between">
<dt className="text-neutral-500">Submitted</dt>
<dd>{new Date().toLocaleString()}</dd>
</div>
</dl>
</div>
<div className="flex gap-3 justify-center">
{onViewSubmission && (
<button
onClick={() => onViewSubmission(submissionId)}
className="px-4 py-2 bg-emerald-500 text-white rounded-lg"
>
View Submission
</button>
)}
{onNewDocument && (
<button
onClick={onNewDocument}
className="px-4 py-2 border border-neutral-300 rounded-lg"
>
New Document
</button>
)}
</div>
</div>
);
}