Troubleshooting: GuestAgreementForm — Blank Page / React Error #310

Date Resolved: April 1, 2026
Affected URL: https://travelagreement.artinvoyage.com/GuestAgreementForm?id=...&token=...
Symptom: Guests visiting the agreement portal saw a completely blank white page. The email verification screen never rendered.


Error Message

The browser console showed:

Minified React error #310

React error #310 means: "Rendered more hooks than during the previous render."
This is a violation of the Rules of Hooks.


Root Cause

In pages/GuestAgreementForm.jsx, a useEffect hook was placed after multiple early conditional return statements in the component function body.

React requires that all hooks must be called unconditionally, before any return statements. If a hook appears after a return, React may or may not reach it depending on state, causing a different number of hooks to be called on different renders — which crashes the component.

What the broken code looked like:

export default function GuestAgreementForm() {
  // ... all the useState declarations ...

  useEffect(() => { /* fetch agreement */ }, [agreementId]);
  useEffect(() => { window.scrollTo(0, 0); }, [step]);

  if (loading) return <LoadingSpinner />;       // ← early return #1
  if (!data)   return <NotFoundScreen />;       // ← early return #2
  if (data?.is_voided) return <VoidedScreen />; // ← early return #3

  // ⚠️ THIS WAS THE PROBLEM — useEffect AFTER early returns
  useEffect(() => {
    if (submitted) {
      const timer = setTimeout(() => {
        window.location.href = "https://artinvoyage.com";
      }, 4000);
      return () => clearTimeout(timer);
    }
  }, [submitted]);
}

On the initial render, loading is true, so the component returned early — before reaching the misplaced useEffect. On subsequent renders (after data loaded), React tried to call that extra hook and threw error #310, crashing the entire component and rendering a blank page.


The Fix

Moved the misplaced useEffect up in the component, above all conditional return statements:

export default function GuestAgreementForm() {
  // ... all useState declarations ...

  useEffect(() => { /* fetch agreement */ }, [agreementId]);
  useEffect(() => { window.scrollTo(0, 0); }, [step]);

  // ✅ FIXED — moved here, before any conditional returns
  useEffect(() => {
    if (submitted) {
      const timer = setTimeout(() => {
        window.location.href = "https://artinvoyage.com";
      }, 4000);
      return () => clearTimeout(timer);
    }
  }, [submitted]);

  // All conditional returns now follow AFTER all hooks
  if (loading) return <LoadingSpinner />;
  if (!data)   return <NotFoundScreen />;
  if (data?.is_voided) return <VoidedScreen />;
}

Key Rule to Remember

Never place a useEffect, useState, useRef, or any other React hook after a conditional return statement.

All hooks must be declared at the top of the component function, before any branching logic or early returns. This is a hard rule enforced by React — violating it causes error #310 and a blank page.


What Was NOT the Cause

  • The utils/currency.js file (investigated and ruled out)
  • The guestAgreement backend function (tested and confirmed working — returns 200)
  • The 405 errors in the console (harmless Base44 platform-level auth logging calls)
  • CORS headers (the backend was already responding correctly)