Phone Book (Form + Sorted Table)
Difficulty: Core
Problem Statementβ
Build a form to add users (first name, last name, phone). Display entries in a table sorted by last name.
Live Demoβ
Try the running app below β this is the target behavior for the challenge.
Phone Book (Form + Sorted Table) β Live Demo
| First | Last | Phone |
|---|
What Interviewers Expectβ
- Controlled inputs for all fields.
- Prevent default on submit.
- Immutable append + sort.
- Reset form after submit.
Step-by-Step Implementationβ
Step 1 β Form state objectβ
Keep one object for controlled inputs.
const [form, setForm] = useState({ first: '', last: '', phone: '' });
Step 2 β Submit handlerβ
Append entry, sort by last name, clear form.
setEntries((prev) => [...prev, form].sort((a,b) => a.last.localeCompare(b.last)));
Step 3 β Render tableβ
Map entries into <tr> rows with stable keys.
{entries.map((e, i) => <tr key={`${e.phone}-${i}`}>...</tr>)}
Complete Implementationβ
Copy-paste ready single-file solution. Use this as your reference after attempting the challenge yourself, or paste it into CodeSandbox / StackBlitz to run locally.
App.jsx
import React, {useState} from 'react';
export default function App() {
const [entries, setEntries] = useState([]);
const [form, setForm] = useState({first: '', last: '', phone: ''});
const onSubmit = (e) => {
e.preventDefault();
if (!form.first || !form.last || !form.phone) return;
setEntries((prev) =>
[...prev, form].sort((a, b) =>
a.last.localeCompare(b.last, undefined, {sensitivity: 'base'}),
),
);
setForm({first: '', last: '', phone: ''});
};
return (
<div>
<form onSubmit={onSubmit} style={{display: 'grid', gap: '0.5rem', maxWidth: 320}}>
<input
placeholder="First name"
value={form.first}
onChange={(e) => setForm({...form, first: e.target.value})}
/>
<input
placeholder="Last name"
value={form.last}
onChange={(e) => setForm({...form, last: e.target.value})}
/>
<input
placeholder="Phone"
value={form.phone}
onChange={(e) => setForm({...form, phone: e.target.value})}
/>
<button type="submit">Add User</button>
</form>
<table style={{width: '100%', marginTop: '1rem', borderCollapse: 'collapse'}}>
<thead>
<tr>
<th style={{border: '1px solid #ccc', padding: '0.4rem'}}>First</th>
<th style={{border: '1px solid #ccc', padding: '0.4rem'}}>Last</th>
<th style={{border: '1px solid #ccc', padding: '0.4rem'}}>Phone</th>
</tr>
</thead>
<tbody>
{entries.map((entry, index) => (
<tr key={`${entry.phone}-${index}`}>
<td style={{border: '1px solid #ccc', padding: '0.4rem'}}>{entry.first}</td>
<td style={{border: '1px solid #ccc', padding: '0.4rem'}}>{entry.last}</td>
<td style={{border: '1px solid #ccc', padding: '0.4rem'}}>{entry.phone}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Final Checklistβ
- UI works for primary flow
- Edge cases handled (empty/disabled/loading where relevant)
- State updates are immutable
- Components are readable and reasonably split
- You can explain trade-offs out loud in an interview
Next Challengeβ
Continue to the next item in the sidebar when you're comfortable implementing this from scratch in 30β45 minutes.