88 lines
3.6 KiB
Markdown
88 lines
3.6 KiB
Markdown
---
|
|
title: "React Query — enabled: !!value Silent Skip on Empty String"
|
|
aliases: [react-query-enabled, react-query-silent-skip, usequery-disabled]
|
|
tags: [react, react-query, frontend, debugging, gotcha, javascript]
|
|
sources:
|
|
- "daily/2026-04-30.md"
|
|
created: 2026-04-30
|
|
updated: 2026-04-30
|
|
---
|
|
|
|
# React Query — `enabled: !!value` Silent Skip on Empty String
|
|
|
|
When a React Query hook uses `enabled: !!someId` as a condition to fire the request, passing an empty string `""` as `someId` silently disables the query — `!!""` is `false`. The query never fires, the loading state resolves immediately with `undefined` data, and the UI shows nothing without any error or console warning.
|
|
|
|
## Key Points
|
|
|
|
- **`!!""` is `false`** — empty string is falsy in JavaScript; React Query treats it as "disabled"
|
|
- **Symptom:** component renders, no network request is made, data is `undefined` — looks like an API error but there is no request at all
|
|
- **Diagnosis:** in React Query DevTools or browser network tab — if there's no request for a query that should have fired, check `enabled` condition for falsy values
|
|
- **Fix option A:** use a separate hook or endpoint that doesn't require the ID (e.g., `GET /clients/all-projects` that returns all projects without a client filter)
|
|
- **Fix option B:** use `enabled: someId !== null && someId !== undefined` instead of `enabled: !!someId` to allow empty-string IDs
|
|
|
|
## Details
|
|
|
|
### The Failure Pattern
|
|
|
|
```typescript
|
|
// useProjects hook — requires clientId
|
|
const { data: projects } = useQuery(
|
|
["projects", clientId],
|
|
() => fetchProjects(clientId),
|
|
{ enabled: !!clientId } // ← silently disabled when clientId === ""
|
|
);
|
|
|
|
// In a form where the user hasn't selected a client yet:
|
|
const [clientId, setClientId] = useState(""); // empty string default
|
|
// → query never fires, projects is undefined, dropdown is empty
|
|
```
|
|
|
|
### Fix A: Separate "All Projects" Endpoint
|
|
|
|
When the UI needs ALL projects regardless of client selection, create a separate endpoint and hook that doesn't require a `clientId`:
|
|
|
|
```typescript
|
|
// New hook — no clientId required
|
|
export function useAllProjects() {
|
|
return useQuery(["projects", "all"], fetchAllProjects); // always enabled
|
|
}
|
|
|
|
// New backend endpoint
|
|
// GET /clients/all-projects → returns all projects user has access to
|
|
```
|
|
|
|
This avoids changing the `enabled` logic and makes the "fetch all" intent explicit.
|
|
|
|
### Fix B: Explicit Null Check
|
|
|
|
When the ID is legitimately optional but not always empty string:
|
|
|
|
```typescript
|
|
const { data: projects } = useQuery(
|
|
["projects", clientId],
|
|
() => fetchProjects(clientId),
|
|
{ enabled: clientId !== null && clientId !== undefined }
|
|
// now "" (empty string) is allowed and the query fires
|
|
);
|
|
```
|
|
|
|
### Common Falsy Values to Watch For in `enabled`
|
|
|
|
| Value | `!!value` | Often means |
|
|
|-------|-----------|-------------|
|
|
| `""` | `false` | Default state, unselected dropdown |
|
|
| `0` | `false` | First item in a zero-indexed list |
|
|
| `null` | `false` | Not yet loaded |
|
|
| `undefined` | `false` | Not yet loaded |
|
|
| `"0"` | `true` (string!) | String zero — may be surprising |
|
|
|
|
When in doubt, use `enabled: value !== null && value !== undefined` rather than `enabled: !!value`.
|
|
|
|
## Related Concepts
|
|
|
|
- [[wiki/concepts/zustand-async-hydration]] — another silent timing bug in React where state isn't ready when components mount
|
|
- [[wiki/tech-patterns/react-vite-typescript]] — React patterns in Oliver projects
|
|
|
|
## Sources
|
|
|
|
- [[daily/2026-04-30.md]] — Brief form: projects dropdown empty because `useProjects('')` was disabled by `enabled: !!clientId`; fixed with `useAllProjects()` hook + `GET /clients/all-projects` endpoint
|