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: defaultProps still 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

RemovedReplacement
ReactDOM.rendercreateRoot(el).render(<App />)
ReactDOM.hydratehydrateRoot(el, <App />)
ReactDOM.unmountComponentAtNoderoot.unmount()
ReactDOM.findDOMNodeUse refs
String refs (ref="myRef")useRef or callback refs
react-test-renderer@testing-library/react
Module pattern factoriesRegular function components

Library Compatibility

Some libraries need updates for React 19. Check compatibility before upgrading.

LibraryReact 19 StatusNotes
React Router v7Compatiblev6 works but v7 recommended
TanStack Query v5Compatiblev4 may have issues
Framer MotionCompatiblev11+
React Hook FormCompatiblev7.54+
Radix UICompatibleLatest versions
styled-componentsCompatiblev6+
ZustandCompatiblev4+
Redux ToolkitCompatiblev2+

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

  1. Install React DevTools browser extension
  2. Open DevTools, go to “Profiler” tab
  3. Click record, interact with your app, stop recording
  4. 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

ProblemSymptomFix
Object/array literals in JSXChild re-renders every timeMove to useMemo or outside component (or use Compiler)
Context with frequent updatesMany components re-renderSplit context, use Zustand for frequent updates
Large lists without virtualizationScroll lagUse @tanstack/react-virtual
Expensive computation in renderSlow rendersuseMemo or move to a worker (or use Compiler)
Missing key or index as keyIncorrect updates, lost stateUse 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 memo calls.

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