Initializing Enclave...

How to Fix 'Cannot read properties of null (evaluating props.data)' in React

Bug Severity: CRITICAL | UX/User Impact: FULL OUTAGE | Time to Fix: 10 mins

TL;DR

  • What broke: A React component attempted to read props.data while props itself (or the parent-passed value) was null — either due to an async data fetch not yet resolved, a conditional render race, or a misrouted component tree passing null explicitly.
  • How to fix it: Guard the render path with an early null check, use optional chaining (props?.data), and ensure parent components pass a defined default prop or a loading state before the child mounts.
  • Use the Client-Side Sandbox above to paste your failing component and auto-refactor it with null guards and PropTypes validation injected automatically.

The Incident (What Does the Error Mean?)

Raw error output:

TypeError: Cannot read properties of null (evaluating 'props.data')
    at YourComponent (YourComponent.jsx:14:18)
    at renderWithHooks (react-dom.development.js:14985)
    at mountIndeterminateComponent (react-dom.development.js:17811)

This is a hard crash. React's reconciler throws synchronously during the render phase. The component tree below this node is completely unmounted. If no Error Boundary wraps this subtree, the entire React root goes blank — white screen, zero user feedback.

The two most common triggers:

  1. Async data race: Parent fetches data, passes null as initial state, child renders before the fetch resolves.
  2. Explicit null prop: A parent conditionally renders the child but passes null or undefined as the data prop value instead of gating the render.

The Blast Radius

This is not a silent warning. React does not recover from render-phase TypeErrors without an Error Boundary. The blast radius:

  • Full subtree unmount. Every child component below the throwing component is destroyed.
  • State loss. Any local state in sibling or child components within the same boundary is wiped.
  • User session disruption. If this component sits in a critical path (dashboard, checkout, auth flow), the user hits a blank screen with no recovery path.
  • Silent failure in production. Without Sentry or a structured Error Boundary with logging, this crash produces zero user-visible error message and zero server-side signal — it dies entirely in the client.

Compounding risk: If this component is rendered inside a list (Array.map), a single null item in the data array propagates the crash across the entire list render.


How to Fix It

Basic Fix — Optional Chaining + Early Return Guard

The minimum viable fix. Stops the crash. Does not address the upstream data contract.

 function UserCard(props) {
-  const name = props.data.name;
-  const email = props.data.email;
+  if (!props || !props.data) {
+    return null; // or return <LoadingSkeleton />
+  }
+  const name = props.data.name;
+  const email = props.data.email;

   return (
     <div>
       <h2>{name}</h2>
       <p>{email}</p>
     </div>
   );
 }

Alternatively, using optional chaining with nullish coalescing for inline access:

 function UserCard(props) {
-  const name = props.data.name;
+  const name = props?.data?.name ?? 'Unknown';
-  const email = props.data.email;
+  const email = props?.data?.email ?? 'No email provided';

   return (
     <div>
       <h2>{name}</h2>
       <p>{email}</p>
     </div>
   );
 }

Enterprise Best Practice — Default Props + PropTypes + Error Boundary

The basic fix stops the bleed. This pattern prevents the wound from opening in the first place and gives your monitoring stack something to catch.

Step 1: Define a strict data contract with PropTypes and defaultProps.

 import PropTypes from 'prop-types';

 function UserCard({ data }) {
+  if (!data) return <div className="skeleton" aria-busy="true" />;

   return (
     <div>
       <h2>{data.name}</h2>
       <p>{data.email}</p>
     </div>
   );
 }

+UserCard.defaultProps = {
+  data: null,
+};
+
+UserCard.propTypes = {
+  data: PropTypes.shape({
+    name: PropTypes.string.isRequired,
+    email: PropTypes.string.isRequired,
+  }),
+};

 export default UserCard;

Step 2: Wrap the subtree in an Error Boundary with telemetry.

+import { ErrorBoundary } from 'react-error-boundary';
+import { captureException } from '@sentry/react';

 function App() {
   return (
+    <ErrorBoundary
+      FallbackComponent={({ error }) => <div>Something went wrong: {error.message}</div>}
+      onError={(error, info) => captureException(error, { extra: info })}
+    >
       <UserCard data={userData} />
+    </ErrorBoundary>
   );
 }

Step 3: Fix the upstream async race in the parent.

 function ParentComponent() {
   const [userData, setUserData] = useState(null);

   useEffect(() => {
     fetchUser().then(setUserData);
   }, []);

-  return <UserCard data={userData} />;
+  if (!userData) return <LoadingSkeleton />;
+  return <UserCard data={userData} />;
 }

This is the correct fix. Don't mount the child until the data contract is satisfied. Optional chaining in the child is a safety net, not the primary defense.


💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your component trees, API response shapes, and internal data structures. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing component into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

This class of bug is entirely preventable at the static analysis layer. Ship none of these to production:

1. ESLint rules — enforce null safety at write time.

Add to your .eslintrc:

{
  "rules": {
    "react/prop-types": "error",
    "no-unsafe-optional-chaining": "error"
  }
}

2. TypeScript — the permanent fix.

Migrate the component to TypeScript with a strict interface. tsc --strict will flag props.data.name as a compile error when data is typed as UserData | null.

-function UserCard(props) {
+interface UserData { name: string; email: string; }
+interface UserCardProps { data: UserData | null; }
+
+function UserCard({ data }: UserCardProps) {
+  if (!data) return null;

3. Storybook + MSW for async state coverage.

Write a story for the null data state explicitly. If your Storybook CI fails on the null story, the bug never reaches staging.

export const LoadingState = () => <UserCard data={null} />;

4. React Testing Library — test the null path.

it('renders skeleton when data is null', () => {
  render(<UserCard data={null} />);
  expect(screen.queryByRole('heading')).not.toBeInTheDocument();
});

If this test exists in your pipeline, the null crash cannot reach production.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →