--- description: Use Relay @required to make expected-present nullable fields non-null globs: "**/*.tsx" alwaysApply: false --- # Use `@required` for expected-present fields GraphQL schemas mark many fields nullable defensively, but at a given call site you usually **expect** a value. When a field is nullable in the schema but the component cannot meaningfully render without it, annotate it with `@required` so the **generated type is non-null**. This keeps typing consistent: callers stop threading `?.` / `?? ""` / `!` through code that always expects data, and a genuinely-missing value becomes a real signal instead of a silently-empty UI. Choose the action by what should happen when the value is actually absent: - `@required(action: THROW)` — the value is an invariant for this view (a page's root entity, the `currentTrustCenter` a portal is built around). A null throws on read and propagates to the nearest error boundary. Field becomes non-null. - `@required(action: LOG)` — a missing value should degrade gracefully: the null bubbles to the nearest `@required` ancestor (or nulls the fragment data) and Relay logs it. Use when the surrounding UI can render a fallback. - `@required(action: NONE)` — bubble nullability without logging; rarely needed. ```graphql # GOOD — the view is built around this entity; THROW makes it non-null currentTrustCenter @required(action: THROW) { organization { name # already String! — no @required needed logo { downloadUrl } # legitimately optional — leave nullable } } ``` ```tsx // GOOD — non-null typing falls out of @required; no defensive chaining const { organization } = data.currentTrustCenter; const logoUrl = organization.logo?.downloadUrl ?? undefined; // logo stays optional ``` Do NOT use `@required` to silence nullability on fields that are *genuinely* optional (avatar, logo, optional description) — those keep their nullable type and get a real empty/fallback state. Do NOT use the `THROW` as control flow for an expected-empty case (it is an error path, not a branch). Do NOT annotate fields the schema already declares non-null (`String!`, `Organization!`). See `contrib/claude/relay.md` (Fragments → Required fields).