Skip to main content

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
FirstLastPhone

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.