AWS Builder Center

Defined but Never Wired: The Bug That Never Throws an Error

The CSS exists, the hook does not. A named bug class from six real production bugs I shipped, and the rule that catches it.

There is a bug class that never throws an error, never logs a warning, never fails a test, and never turns a light red. It just quietly does nothing, and you find out weeks later when a client asks why a feature is missing.
I have a name for it now, because I have shipped it enough times to deserve one: defined but never wired.
The CSS exists. The hook does not. The data field exists. The render does not. The import exists. The call does not. Same bug, different costume, every single time.
1
2
3
4
5
6
7
the thing that exists the thing that does not
--------------------------- ---------------------------
.fade-section { ... } -> the useFadeIn hook that applies it
import { usePathname } -> const pathname = usePathname()
image: "formations/x.jpg" -> <Image src={formation.image} />
telephone: string -> the MAX_LEN check covering it
export function Card() -> any file that imports Card
This is not a syntax error. The build passes. TypeScript is happy. It is the only category of bug I know of where the absence of an error is the symptom.

Example 1: the navbar that never knew where it was

Every nav link on every page looked identical. No active state, no highlight, nothing. On a site with a homepage, a formations section, a prestations section and an admin panel.
The file had this, at the very top:
1
import { usePathname } from 'next/navigation'
And then, in the component body, nothing. The import was there. The call was not.
No ESLint error, because the import was used. Technically. It was passed nowhere, stored nowhere, and read nowhere. The compiler was correct: I did import it.
1
2
3
4
5
6
7
// The fix
const pathname = usePathname()

// then, per link
className={pathname === '/'
? 'text-green font-semibold'
: 'text-charcoal'}
Prevention: after adding any import that returns a value, immediately verify the returned value is assigned to something and read somewhere. An unused next/navigation import produces no console error. The only signal is the missing behaviour, and you will find it by looking at the page, not the terminal.

Example 2: CSS for an animation that could never fire

This one is my favourite, because the CSS was beautiful. It was in globals.css, labelled, committed, reviewed:
1
2
3
4
5
6
7
8
9
.fade-section {
opacity: 0;
transform: translateY(20px);
transition: opacity 600ms ease, transform 600ms ease;
}
.fade-section.visible {
opacity: 1;
transform: translateY(0);
}
Eight sections below the hero were supposed to fade in as you scrolled. They did not fade in. They were simply there.
The useFadeIn hook — the thing that observes the element and adds .visible — was never created. I wrote the transition and never wrote the trigger.
Prevention: never commit half of an animation system. If you add the CSS class, add a stub for the consuming hook in the same commit, even if the hook body is empty, and leave a TODO in the CSS pointing at the hook's path. The gap between "the effect is defined" and "the effect is wired" is exactly where the work disappears.

Example 3: the data field nobody rendered

Every course card on the homepage was a white box with a title and some text. No images. Not broken images, not placeholder grey — no image element at all.
The data file had it:
1
2
3
4
export const formations = [
{ id: '3-en-1', title: '3 en 1', image: '/images/formations/3-en-1.jpg' },
// ...
]
And the component read it from props, then never put it in the DOM. The field existed, the prop existed, the <Image> tag did not.
The fix was a <div> with a relative aspect box and <Image fill> inside it, which is now part of the component from the first commit.
Prevention: when a card component renders an entity that has an image field in its data type, the image render belongs in the initial component. Even as a coloured placeholder div. Build the whole shape once, then style it.

Example 4: the validator that checked three fields and forgot the fourth

This is the dangerous one, because it is not a missing feature, it is a missing check.
I had a length guard before sanitisation, so nobody could post a 50,000-character payload. It looked solid. It validated nom, message and reference.
It did not validate telephone. So a phone number of ten thousand digits went straight through, past the guard, past sanitisation, into the database.
No error. No log. Just a row with an absurd value in the one field the clinic actually calls people back on.
Prevention: when adding a length or format check, iterate over all user-supplied string fields rather than listing them by hand. A hand-written list is a list that will be one short within a month. And validate format at both boundaries: a client regex for the user experience, a server regex because the client is not a security control.

Example 5: two rendering paths for the same thing

I fixed a hover zoom effect on FormationCard.jsx. Tested it. Worked. Shipped it.
Nothing changed on the homepage.
The homepage grid, FormationsGrid.jsx, renders its own inline card markup. It never imports FormationCard.jsx. There were two components rendering the same entity, and the fix only landed in one of them.
Prevention: before editing a component, grep for who actually imports it. If the answer is "the page renders its own inline version", you have just fixed dead code. One card per entity, one import path, no second renderer.

Example 6: the scaffold placeholder that shipped

Every single image field in two data files was null. Not most of them. All seventeen.
They were scaffold placeholders. I never filled them in, and nothing complained, because null is a valid value for an image field when nothing renders it.
The visible symptom was worse than a broken image would have been: the most prominent person on a clinic website — the founder — rendered as a grey circle with the letter M. On a medical professional's site, that is not a bug, it is a credibility problem.
Prevention: add a build-time check that fails loudly. A single grep before deploy, in the pre-deploy script:
1
2
3
# WHY a hard exit: if this prints anything, the deploy does not proceed.
grep -r "image: null" data/ && exit 1
echo "no null images"
And the general version: a placeholder you intend to fill in is a placeholder you must make impossible to ship. Either the lint fails or the grep exits non-zero. Feelings are not a deploy gate.

The rule I actually keep now

Anything that defines a value needs a second step that consumes it, and the second step needs a way to fail loudly when it is missing.
DefinedNever wiredMake it fail
CSS transition classthe hook that adds itTODO in the CSS pointing at the hook path
Data fieldthe render that reads itbuild-time grep for the placeholder value
Importthe call that uses itlint rule for assigned-but-unread
Field in a schemathe validator that covers itloop over fields, never a hand-written list
Componentthe page that imports itgrep for the import path before editing
The pattern is always the same. I built half a thing, the half I built was correct, and the half I did not build has no opinion about existing. That is why it survives a code review, survives a build, and ships.
None of these six examples would have been caught by a test suite. I do not have a test suite. What caught them was looking at the page and asking why the thing I obviously wrote is not on the screen.
That is a worse strategy than testing. But it is the one I have, and it has a much better hit rate than reading my own diff.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article