TypeScript and React work best when props describe the component's contract, state is modelled as a union and events use the right DOM types. The goal is fewer impossible states, not more type annotations.
Key takeaways
- Type props explicitly; avoid
React.FCunless you need its specific behaviour. - Use
ComponentProps<"button">to extend native elements without losing attributes. - Children can be
React.ReactNode; a render prop is a function type. - Prefer the simplest boundary that keeps data and authorisation on the server.
What matters in practice
- Discriminated unions handle async UI states better than separate booleans.
- Server and client components have different constraints; do not pass functions across the boundary.
How to implement it
- Type the component boundary. Props in, events out; keep internal state private.
- Model async state. Use a union for loading, error and success.
- Use native element props. Extend
ComponentPropsfor buttons, inputs and links. - Type events correctly. Use
React.ChangeEventand friends. - Test the contract. Render the states and interactions that matter.
- Review the boundary. Keep
use clientat the leaf and pass serialisable props.
A working example
type AsyncState<T> =
| { status: "loading" }
| { status: "error"; message: string }
| { status: "success"; data: T };
type ProfileProps = {
state: AsyncState<User>;
onRetry: () => void;
};
export function Profile({ state, onRetry }: ProfileProps) {
if (state.status === "loading") return <Spinner />;
if (state.status === "error") return <ErrorState message={state.message} onRetry={onRetry} />;
return <UserCard user={state.data} />;
}
Production checklist
| Area | Check |
|---|---|
| Props | Explicit, narrow and documented |
| Events | Typed DOM events, not any |
| State | Discriminated unions for async states |
| Native elements | ComponentProps for extension |
| Boundaries | Serialisable props across server/client |
| Tests | Each visual state and the main interaction |
Common mistakes
- Using
React.FCand losing the ability to pass some props. - Typing every event as
any. - Modelling loading and error as independent booleans.
- Passing a function from a server component to a client component.
When this is the wrong tool
Every tool has a boundary. If the team is small, the use case is simple and the operational cost is high, a simpler stack may be better. The right choice is the one you can debug, secure and hand over without the original author.
Rollout plan
- Prove one slice. Use TypeScript with React for one real route or feature, not the whole product.
- Set the boundary. Write down what stays on the server, what runs on the client and how data crosses the boundary.
- Add the safety net. Types, validation, tests and error handling around the new slice.
- Measure. Check bundle size, latency, error rate and the developer experience before expanding.
- Document and hand over. Record the pattern, the pitfalls and the version constraints.
Testing strategy
| Layer | What to test | Why |
|---|---|---|
| Types | Strict compile and no unjustified any | Catches contract drift early |
| Unit | Pure logic, validation and state transitions | Fast feedback |
| Integration | API, database and auth boundaries | Most production failures live here |
| End-to-end | The critical user journey | Proves the wiring |
| Operations | Logs, errors, retries and rollback | A release is not finished at deploy |
Operational checklist
- Pin the runtime and dependency versions; test upgrades in a branch.
- Keep secrets on the server and validate every external payload.
- Add structured logs with a request ID, but never log tokens or personal data.
- Set a performance or bundle budget and check it in CI.
- Write a short runbook for the most likely failure.
Frequently asked questions
Usually no. Type the props object directly so you control children and generic behaviour. React.FC is fine if you specifically want its implicit children and return type.
Use React.ComponentProps<"button"> and spread the rest props. Omit conflicting props if your wrapper controls them.
Use a generic function with a constrained type parameter and preserve inference in the props. Avoid casting inside the component.
Not for props your own code controls. Validate data at the API, form or storage boundary before it becomes props.
Sources and further reading
- React documentation (react.dev)
- TypeScript handbook (typescriptlang.org)
- Next.js server and client components (nextjs.org)
Related reading
Need an implementation review?
Send the repository or the design. I will review the boundary, the failure modes and the parts that will be expensive to change later.
How to cite this guide: Nur Hossain Repon, “TypeScript with React: complete developer guide (2026)”, Nhrepon Portfolio, reviewed October 2026, https://nhrepon.com/posts/typescript-with-react-complete-developer-guide-2026.





Comments
No comments yet.