remix-run/react-router · error · Error

No contact found for ${id}

Error message

No contact found for ${id}

What it means

This is a domain error thrown by updateContact() in tutorials/address-book/app/data.ts when fakeContacts.get(id) returns a falsy value (undefined/null). It guards against blindly merging updates onto a missing record in the in-memory fakeContacts store, which would otherwise create a partial/garbage contact. Because getContact() (which does not throw) is the read path, callers learn of a missing record only when they attempt to mutate it.

Source

Thrown at tutorials/address-book/app/data.ts:88

      keys: ["first", "last"],
    });
  }
  return contacts.sort(sortBy("last", "createdAt"));
}

export async function createEmptyContact() {
  const contact = await fakeContacts.create({});
  return contact;
}

export async function getContact(id: string) {
  return fakeContacts.get(id);
}

export async function updateContact(id: string, updates: ContactMutation) {
  const contact = await fakeContacts.get(id);
  if (!contact) {
    throw new Error(`No contact found for ${id}`);
  }
  await fakeContacts.set(id, { ...contact, ...updates });
  return contact;
}

export async function deleteContact(id: string) {
  fakeContacts.destroy(id);
}

[
  {
    avatar:
      "https://sessionize.com/image/124e-400o400o2-wHVdAuNaxi8KJrgtN3ZKci.jpg",
    first: "Shruti",
    last: "Kapoor",
    twitter: "@shrutikapoor08",
  },
  {

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Before updateContact(), confirm the contact exists by awaiting getContact(id) and returning a 404 / 'Not Found' Response when it is falsy — mirrors the route's existing read-path pattern.
  2. If the record legitimately may be absent, catch the error in the action and return a 404 Response so React Router renders the boundary instead of crashing the route.
  3. Ensure the id passed to updateContact comes from params.contactId (matching the route definition) and not from a stale client cache or form field.
  4. If using the tutorial with HMR, restart the dev server so fakeContacts seed data is consistent with the ids in the URL.

Example fix

// before
export async function updateContact(id: string, updates: ContactMutation) {
  const contact = await fakeContacts.get(id);
  if (!contact) {
    throw new Error(`No contact found for ${id}`);
  }
  await fakeContacts.set(id, { ...contact, ...updates });
  return contact;
}

// after (guard in the action, fail with a Response)
export async function updateContact(id: string, updates: ContactMutation) {
  const contact = await fakeContacts.get(id);
  if (!contact) {
    throw new Response(`No contact found for ${id}`, { status: 404 });
  }
  await fakeContacts.set(id, { ...contact, ...updates });
  return contact;
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate existence before mutating, in the route action or caller.
import { getContact } from "~/data";

export async function action({ params, request }: ActionFunctionArgs) {
  const id = params.contactId!;
  const existing = await getContact(id);
  if (!existing) {
    throw new Response("Contact not found", { status: 404 });
  }
  const formData = await request.formData();
  await updateContact(id, Object.fromEntries(formData));
  return redirect(`/contacts/${id}`);
}

Type guard

// Narrow a possibly-missing contact before update.
import type { ContactRecord } from "~/data";

const isContact = (value: unknown): value is ContactRecord =>
  typeof value === "object" &&
  value !== null &&
  "id" in value &&
  typeof (value as ContactRecord).id === "string";

Try / catch

// In an action: let it surface as a 404 Response instead of an uncaught Error.
try {
  await updateContact(id, updates);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("No contact found for ")) {
    throw new Response(err.message, { status: 404 });
  }
  throw err; // unexpected — propagate
}

Prevention

When it happens

Trigger: Calling updateContact(id, updates) with an id that was never created by createEmptyContact(), was removed by deleteContact() (fakeContacts.destroy(id)), or whose value differs from the URL param after navigation (stale link, manual URL edit, concurrent deletion). Also triggered by a loader/action receiving a params.contactId that does not match any key in the fakeContacts store.

Common situations: User opens a contact edit route via a bookmarked or refreshed URL for a contact that was deleted in another tab (fakeContacts.destroy ran); tutorial tinkerers call updateContact directly with a hard-coded id; the route param is `contacts/:contactId` but the action passes `params.id`; the seed data was reset (HMR/module reload recreated fakeContacts) so prior ids no longer exist.


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/86540ce7ba645d38. Report an issue: GitHub.