When building the Pharmacokinetics Grapher—an educational web app for visualizing medication concentration curves—I faced a common problem: users needed to input multiple drug prescriptions, but typing each one manually into a form was tedious and error-prone.
The solution? A Vue 3 JSON import modal—a reusable component that lets users paste structured data and validates everything before importing. Here’s how I built it, what I learned, and the iterative process that got there.
The Problem: Manual Data Entry vs. Bulk Import
The Pharmacokinetics Grapher requires detailed pharmacokinetic (PK) parameters for each drug:
- Drug name
- Dose per administration
- Dosing frequency (bid, tid, q6h, etc.)
- Dosing times (HH:MM format)
- Half-life (hours)
- Time to peak concentration (Tmax, hours)
- Absorption time (uptake, hours)
- Optional metabolite half-life
Filling out 8+ fields for each drug is fine for one prescription. But for testing, comparison, or educational demonstrations, users wanted to import multiple drugs at once. Manually entering acetaminophen, ibuprofen, amoxicillin, and sertraline? That’s repetitive and frustrating.
The Journey: From Requirements to Implementation
Phase 1: Clarifying the Ask
I started by asking: Does the user want real medication data or synthetic test data?
The answer: real data. So I researched actual PK parameters from pharmacy references and created a reference database:
| Medication | Dose | Frequency | Half-Life | Tmax | Uptake |
|----------|------|-----------|-----------|------|--------|
| Acetaminophen | 500mg | q6h | 2-3h | 0.5-1h | 0.5h |
| Ibuprofen | 200-400mg | q6-8h | 2h | 1-2h | 0.5-1h |
| Amoxicillin | 500mg | tid | 1h | 1-2h | 0.5h |
| Sertraline | 50mg | once daily | 26h | 5.5h | 1-2h |
With real data in hand, I could now design the import system.
Phase 2: Designing the JSON Format
I needed a JSON schema that was:
- Simple — users can paste it easily
- Flexible — handles optional fields (metabolite half-life)
- Structured — validates against the same rules as the form
The result:
{
"prescriptions": [
{
"name": "Acetaminophen (Tylenol)",
"dose": 500,
"frequency": "q6h",
"times": ["06:00", "12:00", "18:00", "00:00"],
"halfLife": 2.5,
"peak": 0.75,
"uptake": 0.5
}
]
}
Simple. Array of prescriptions. Field names match the app’s internal type definition. Optional fields can be omitted.
Phase 3: Building the Modal Component
Now the real work: the ImportPrescriptions.vue component.
Key requirements:
- Accept pasted JSON
- Validate JSON syntax in real-time
- Validate each prescription using existing validation logic
- Show success/failure feedback
- List detailed errors (which rows failed, why)
- Import to localStorage on success
I started with the JSON validation. Initially, I wrote:
const isValidJson = computed(() => {
try {
jsonInput.value.trim() && JSON.parse(jsonInput.value)
return true
} catch {
return false
}
})
Linting caught this immediately: “Expected expression to be used” — the AND operator result was never used. The fix:
const isValidJson = computed(() => {
try {
if (!jsonInput.value.trim()) return false
JSON.parse(jsonInput.value)
return true
} catch {
return false
}
})
Lesson: Linters catch subtle bugs. Listen to them.
Phase 4: Validation and Error Handling
The tricky part: validating multiple prescriptions and collecting all errors, not just stopping at the first failure.
prescriptions.forEach((rx, index) => {
const validation = validatePrescription(rx)
if (validation.valid) {
try {
savePrescription(rx)
importResult.value.success++
} catch (e) {
importResult.value.failed++
importResult.value.errors.push(
`Row ${index + 1} (${rx.name}): Failed to save - ${error}`
)
}
} else {
importResult.value.failed++
importResult.value.errors.push(
`Row ${index + 1} (${rx.name}): ${validation.errors.join(', ')}`
)
}
})
This approach:
- Validates all rows, not just the first one
- Provides context (row number, drug name)
- Reuses existing validation —
validatePrescription()already knows all the rules - Captures save errors — in case localStorage fails
Result: Users see exactly which drugs imported successfully and which failed, with reasons why.
Phase 5: Integration with PrescriptionForm
The final step: wire the import modal into the existing form. I added:
- State ref for modal visibility
- A subdued “or import prescriptions” link below the submit button
- Modal component that emits
@importedon success
<!-- Template -->
<div class="import-link-container">
<button
type="button"
@click="showImportModal = true"
class="import-link"
>
or import prescriptions
</button>
</div>
<!-- Import modal -->
<ImportPrescriptions
v-if="showImportModal"
@imported="handleImportSuccess"
@close="showImportModal = false"
/>
The link is intentionally subdued (gray text, no border, underline) so it doesn’t distract from the main form, but it’s discoverable for users who want bulk import.
Technical Deep Dive: Vue 3 JSON Import Modal
Here’s what makes this pattern reusable for other projects:
1. JSON Validation is Cheap
Validating JSON syntax happens in a computed, so it’s instant as users type. The import button only enables when the JSON is valid:
<button
:disabled="!isValidJson || jsonInput.trim().length === 0"
>
Import
</button>
2. Reuse Existing Validation Logic
Don’t duplicate validation. The import modal calls the same validatePrescription() function that the form uses. This means:
- Consistency: Same rules everywhere
- Maintainability: Update validation once, both work
- Confidence: If the form validates it, the import will too
3. localStorage Integration
Each prescription is saved using the existing savePrescription() function, which handles ID generation and localStorage persistence. No special code needed—the modal just leverages existing infrastructure.
4. User Feedback is Critical
The modal shows:
- Success count: “Successfully imported 3 prescriptions”
- Failure count: “Failed to import 1 prescription”
- Detailed errors: Row number, drug name, specific validation failures
This transparency helps users fix data issues and retry.
Challenges and Solutions
Challenge 1: Linting Errors
The Problem: My initial JSON validation used an AND operator that wasn’t assigned, triggering oxlint’s “expected expression to be used” error.
The Fix: Explicit if statement. More readable anyway.
Challenge 2: Error Collection
The Problem: Stopping at the first error leaves users guessing about other issues in their data.
The Fix: Validate all rows, collect all errors. Users see everything at once and can fix multiple issues in one pass.
Lessons Learned
- Modal components are powerful for side workflows. Importing bulk data is a secondary action—keep it off the main form but easily accessible.
- Reuse validation logic. Don’t write different validators for form and import. Use one source of truth.
- Linters are your friend. Fixed a subtle bug immediately instead of causing issues later.
- Real data matters for testing. Using actual drug PK parameters (from pharmacy references) made testing feel authentic.
- Error context is valuable. Showing row numbers and drug names makes debugging user data problems easier.
Results and Next Steps
What works now:
- ✓ Users can paste JSON with multiple prescriptions
- ✓ Each prescription is validated against the same rules as the form
- ✓ Detailed error feedback for debugging bad data
- ✓ Successful imports are stored in localStorage
- ✓ Modal integrates seamlessly with existing form workflow
Future enhancements:
- CSV import (convert CSV to JSON, then use existing logic)
- Export prescriptions as JSON (mirror of import)
- Pre-populated templates for common drugs
- Database of medications (so users don’t need to research PK parameters)
Key Takeaway
Building a robust import system isn’t just about parsing JSON. It’s about understanding your users’ workflows, leveraging existing validation logic, providing clear feedback, and integrating thoughtfully with the rest of your app. A Vue 3 modal is a perfect vehicle for this—non-disruptive, reusable, and focused.
If you’re building data-heavy applications (CMSs, dashboards, educational tools), consider a similar import pattern. Your users will appreciate the bulk operations capability.
Resources
Full Source Code:
- Pharmacokinetics Grapher on GitHub — Complete source code for the project, including the ImportPrescriptions component, form validation logic, and PK calculations
- ImportPrescriptions.vue — The modal component discussed in this post
- PrescriptionForm.vue — Integration point showing how the import modal connects to the form

Leave a Reply
You must be logged in to post a comment.