Gotchas
Common mistakes, React 19 breaking changes, server/client boundary errors, and performance profiling tips.
Removed APIs in React 19
React 19 removes several long-deprecated APIs. If you’re upgrading from React 18, these will break.
propTypes and defaultProps (Function Components)
// Removed: propTypes on function components
function Button({ label }) { ... }
Button.propTypes = { label: PropTypes.string.isRequired }; // error in React 19
// Use TypeScript instead
function Button({ label }: { label: string }) { ... }
// Removed: defaultProps on function components
Button.defaultProps = { variant: "primary" }; // error in React 19
// Use default parameters instead
function Button({ variant = "primary" }: { variant?: string }) { ... }
Tip:
defaultPropsstill works on class components in React 19, but class components themselves are effectively deprecated. Migrate to functions.
Legacy Context (contextTypes / childContextTypes)
// Removed: the old context API
class Parent extends Component {
static childContextTypes = { ... };
getChildContext() { return { ... }; }
}
// Use createContext instead (available since React 16.3)
const MyContext = createContext(defaultValue);
<MyContext.Provider value={...}>
forwardRef (No Longer Needed)
// Before React 19: needed forwardRef to pass ref to a child
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
// React 19: ref is a regular prop, no wrapper needed
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// Or even simpler
function Input(props: InputProps) {
return <input {...props} />; // ref passes through automatically
}
Other Removals
| Removed | Replacement |
|---|---|
ReactDOM.render | createRoot(el).render(<App />) |
ReactDOM.hydrate | hydrateRoot(el, <App />) |
ReactDOM.unmountComponentAtNode | root.unmount() |
ReactDOM.findDOMNode | Use refs |
String refs (ref="myRef") | useRef or callback refs |
react-test-renderer | @testing-library/react |
| Module pattern factories | Regular function components |
Library Compatibility
Some libraries need updates for React 19. Check compatibility before upgrading.
| Library | React 19 Status | Notes |
|---|---|---|
| React Router v7 | Compatible | v6 works but v7 recommended |
| TanStack Query v5 | Compatible | v4 may have issues |
| Framer Motion | Compatible | v11+ |
| React Hook Form | Compatible | v7.54+ |
| Radix UI | Compatible | Latest versions |
| styled-components | Compatible | v6+ |
| Zustand | Compatible | v4+ |
| Redux Toolkit | Compatible | v2+ |
Gotcha: If a library uses
forwardRef,defaultProps, or string refs internally, it may break on React 19 even if your code doesn’t use those APIs. Check the library’s changelog or issues for React 19 compatibility.
Common Server/Client Boundary Mistakes
Importing Server Components in Client Components
// ClientWrapper.tsx
"use client";
import { ServerData } from "./ServerData"; // this breaks
export function ClientWrapper() {
return <ServerData />; // can't render a server component inside a client component
}
Fix: pass server components as children.
// layout.tsx (server component)
import { ClientWrapper } from "./ClientWrapper";
import { ServerData } from "./ServerData";
export default function Layout() {
return (
<ClientWrapper>
<ServerData /> {/* rendered on server, passed as children */}
</ClientWrapper>
);
}
Passing Non-Serializable Props Across the Boundary
Server components can only pass serializable data to client components. Functions, classes, Dates, Maps, Sets - none of these can cross the boundary as props.
// Server component
async function Page() {
const data = await getData();
return (
<ClientChart
data={data} // OK: plain object
onClick={() => console.log()} // breaks: functions aren't serializable
date={new Date()} // breaks: Date isn't serializable
/>
);
}
Fix: pass serializable data, let the client component create functions and special objects.
// Server component
async function Page() {
const data = await getData();
return (
<ClientChart
data={data}
dateStr={new Date().toISOString()} // serialize as string
/>
);
}
// Client component
"use client";
function ClientChart({ data, dateStr }: Props) {
const date = new Date(dateStr); // reconstruct on client
const onClick = () => console.log(); // create function on client
// ...
}
Using Hooks in Server Components
// This will error - server components can't use hooks
async function ServerPage() {
const [count, setCount] = useState(0); // error!
return <div>{count}</div>;
}
If you need state or effects, the component must be a client component ("use client").
Accidentally Making Everything a Client Component
"use client" cascades. If a layout component is marked "use client", everything it imports is also client-side.
// layout.tsx
"use client"; // this makes EVERYTHING below a client component
import { Sidebar } from "./Sidebar"; // now client
import { MainContent } from "./MainContent"; // now client
import { Footer } from "./Footer"; // now client
Fix: push "use client" as deep as possible. Only the interactive leaves should be client components.
Performance Profiling
React DevTools Profiler
- Install React DevTools browser extension
- Open DevTools, go to “Profiler” tab
- Click record, interact with your app, stop recording
- Look for:
- Long bars = slow renders (> 16ms for 60fps)
- Many renders = unnecessary re-renders
- “Why did this render?” tells you if it was state, props, parent, or context
Identifying Slow Components
// Quick check: add a console.log to see render frequency
function SuspiciousComponent() {
console.log("SuspiciousComponent rendered"); // remove before commit
// ...
}
Common Performance Issues
| Problem | Symptom | Fix |
|---|---|---|
| Object/array literals in JSX | Child re-renders every time | Move to useMemo or outside component (or use Compiler) |
| Context with frequent updates | Many components re-render | Split context, use Zustand for frequent updates |
| Large lists without virtualization | Scroll lag | Use @tanstack/react-virtual |
| Expensive computation in render | Slow renders | useMemo or move to a worker (or use Compiler) |
Missing key or index as key | Incorrect updates, lost state | Use stable, unique IDs as keys |
React.Profiler Component
Measure renders programmatically:
import { Profiler } from "react";
function onRender(
id: string,
phase: "mount" | "update",
actualDuration: number
) {
if (actualDuration > 16) {
console.warn(`Slow render: ${id} took ${actualDuration.toFixed(1)}ms (${phase})`);
}
}
function App() {
return (
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>
);
}
Why Did You Render
The @welldone-software/why-did-you-render package patches React to log unnecessary re-renders in development:
npm install -D @welldone-software/why-did-you-render
// src/wdyr.ts - import BEFORE React
import React from "react";
if (process.env.NODE_ENV === "development") {
const whyDidYouRender = await import("@welldone-software/why-did-you-render");
whyDidYouRender.default(React, {
trackAllPureComponents: true,
});
}
// main.tsx
import "./wdyr"; // must be first import
import { createRoot } from "react-dom/client";
Tip: With React Compiler enabled, most re-render problems go away automatically. Use profiling tools to find algorithmic bottlenecks (O(n^2) operations, unvirtualized lists) rather than hunting for missing
memocalls.
StrictMode Double-Renders
React’s <StrictMode> intentionally double-invokes your components, effects, and reducers in development. This catches:
- Impure render functions (different output on second call)
- Missing effect cleanup (leaked subscriptions, timers)
- Side effects during render (writing to external variables)
// This bug is invisible without StrictMode
let externalCounter = 0;
function BuggyComponent() {
externalCounter++; // side effect during render
return <div>{externalCounter}</div>;
// StrictMode: renders 1, then 2 - exposed the bug
}
Don’t remove StrictMode to “fix” double-rendering. Fix the underlying issue instead. StrictMode does nothing in production builds.
Next: Component Model to review the fundamentals