top of page
Search

The Real Work Starts After the Screen Is Built: How to Connect a Complex React Wizard Form to REST APIs

  • Writer: Mark Kendall
    Mark Kendall
  • 4 hours ago
  • 11 min read



The Real Work Starts After the Screen Is Built: How to Connect a Complex React Wizard Form to REST APIs



A lot of teams think the hard part is getting the screen built.


That is only partly true.


With AI tools like Claude Code, we can move much faster from Figma screens, Jira stories, acceptance criteria, and mock data into a working React form. But once the form is on the screen, the real enterprise work begins.


Now the form has to connect to real APIs.


Now the data has to match.


Now the business rules have to survive the integration.


Now the backend has to support what the UI is asking for.


And this is where many teams slow down.


Not because they cannot write React.


Not because they cannot write APIs.


But because they do not have a clear integration pattern.


This article lays out the practical pattern teams should follow when connecting a complex wizard-based React form to protected REST APIs.



The Scenario



Imagine a multi-step React wizard form.


It has:


  • Multiple screens

  • Lots of questions

  • Conditional fields

  • Business rules

  • Validation rules

  • Figma designs

  • Jira story details

  • Acceptance criteria

  • Mock data

  • Backend REST APIs

  • A database schema

  • Some fields that exist in the UI but not yet in the database

  • Some API fields that do not perfectly match the form



That is not just a UI task.


That is an integration task.


The goal is not simply:


Connect the form to the API.


The better goal is:


Connect the wizard form to the backend REST APIs through a clean integration layer that maps, validates, saves, loads, and documents the differences between the UI model and the API model.


That is the difference between hacking it together and building a repeatable enterprise pattern.



First Principle: Do Not Put API Calls Directly Inside the Form Components



This is the first rule.


Do not scatter fetch() or axios calls across every React component.


That creates a mess quickly.


The UI should not know every detail about:


  • API URLs

  • Headers

  • Auth tokens

  • Request payload shape

  • Response payload shape

  • Backend field names

  • Database field names

  • Retry logic

  • Error translation

  • API gaps

  • Missing fields



The React component should mostly care about the form experience.


It should know:


  • What data to display

  • What fields are required

  • What step the user is on

  • What happens when the user clicks Next, Back, Save, or Submit



The integration details should sit behind a service layer.



The Recommended Pattern



For a complex wizard form, the flow should look like this:

Wizard UI

  ↓

Form State / Wizard Controller

  ↓

Feature Service Layer

  ↓

API Client / Integration Layer

  ↓

Protected REST APIs

  ↓

Backend / Database

This does not always mean server-side middleware.


Sometimes people hear the word “middleware” and think it must be a Node service, .NET service, API gateway, or backend proxy.


Maybe.


But in this case, what we really need first is a frontend feature integration layer.


That layer lives in the React application and protects the wizard from backend complexity.


A good folder structure might look like this:

wizard/

  components/

  state/

  rules/

  api/

    wizardService.ts

    wizardClient.ts

    wizardSchemas.ts

    wizardMappers.ts

    wizardErrors.ts

    mockWizardService.ts

The names do not matter as much as the separation of responsibility.


The important thing is this:


The UI does not directly talk to the raw API.


The UI talks to a feature service.


The feature service talks to the API client.


The mappers translate between the UI model and the API model.


The schemas validate the data.


The rules handle business behavior.


That is the pattern.



Is This Per API or Shared?



Some parts should be shared.


Some parts should be feature-specific.


A shared API client can handle common concerns like:


  • Base URL

  • Auth headers

  • Token handling

  • Standard request wrapper

  • Standard error handling

  • JSON parsing

  • Logging

  • Correlation IDs

  • Timeout behavior



That can be reused across the whole application.


But the wizard-specific pieces should stay close to the wizard feature.


Those include:


  • Wizard form model

  • Wizard request payloads

  • Wizard response payloads

  • Wizard field mappings

  • Wizard validation rules

  • Wizard step logic

  • Wizard-specific error messages

  • Wizard mock service



So the pattern is:

Shared API Infrastructure

  Used by many features


Feature Integration Layer

  Specific to this wizard form

Do not put every wizard rule into a global API client.


Do not put every API detail inside the React component.


Keep the boundary clean.



The Playlist: How to Do the Integration



Here is the step-by-step playlist the team should follow.



Step 1: Confirm What the UI Already Has



Before touching the API, inspect the current wizard.


Document:


  • All wizard steps

  • All form fields

  • Required fields

  • Optional fields

  • Conditional fields

  • Validation rules

  • Business rules

  • Current mock data shape

  • Current submit behavior

  • Current save behavior

  • Current navigation behavior



The team should be able to answer:


What is the UI asking for?


This sounds basic, but many integrations fail because nobody has written down the actual UI data model.



Step 2: Collect the Backend API Evidence



Do not start coding from rumors.


Ask the backend team for concrete API evidence.


You need:


  • Endpoint URLs

  • HTTP methods

  • Auth requirements

  • Required headers

  • Query parameters

  • Route parameters

  • Request payload examples

  • Response payload examples

  • Error response examples

  • Status codes

  • OpenAPI/Swagger documentation if available

  • Working curl commands

  • Database schema

  • Known missing fields

  • Known fields that are not final



The curl commands are especially valuable.


A working curl command proves that the endpoint can be called.


Example:

  -H "Authorization: Bearer <token>" \

  -H "Content-Type: application/json" \

  -d '{ ... }'

Once you have working examples, Claude Code or any engineer can reason much more accurately.



Step 3: Identify the Three Data Models



There are usually three different models involved.



1. UI Form Model



This is what the wizard needs.


It may look clean and user-friendly.


Example:

firstName

lastName

middleName

dateOfBirth

applicationType

hasPriorEnrollment


2. API Model



This is what the REST API accepts and returns.


It may use different names.


Example:

person.first_name

person.last_name

person.dob

workflow.application_type


3. Database Model



This is what actually exists behind the API.


Example:

PERSON.FIRST_NAME

PERSON.LAST_NAME

PERSON.DATE_OF_BIRTH

FORM.APPLICATION_TYPE_CD

These three models are not automatically the same.


Do not pretend they are.


The integration layer exists because these models are different.



Step 4: Build the Field Mapping Table



This is one of the most important artifacts.


Create a table like this:

UI Field | Mock Field | API Field | DB Field | Status | Notes

Example:

firstName | applicant.firstName | person.first_name | PERSON.FIRST_NAME | Mapped | Direct mapping

middleName | applicant.middleName | — | — | Missing | UI has field, backend does not support it yet

status | form.status | workflowStatus | FORM.STATUS_CD | Transform | Enum conversion needed

hasPriorEnrollment | enrollment.prior | priorEnrollmentFlag | ENROLLMENT.PRIOR_FLG | Mapped | Boolean conversion needed

This table becomes the truth.


It shows what is mapped.


It shows what is missing.


It shows what needs transformation.


It shows what requires backend clarification.


Without this table, the team is guessing.


With this table, the team can move.



Step 5: Separate Mapping Rules from Business Rules



This is critical.


A mapping rule is not the same as a business rule.


A mapping rule says:


API field X becomes UI field Y.


A business rule says:


If the user answers Yes to question A, show question B.


A validation rule says:


Question B is required when question A is Yes.


These should not all be mixed together inside React JSX.


A better structure is:

wizard/

  rules/

    stepVisibilityRules.ts

    validationRules.ts

    businessRules.ts

  api/

    wizardMappers.ts

This gives the team a clean place to reason.


It also gives Claude Code a better structure to modify safely.



Step 6: Create the Shared API Client



Most applications should have a shared API client.


This handles common technical concerns.


Example responsibilities:


  • Add auth headers

  • Set content type

  • Use the correct base URL

  • Parse JSON

  • Handle HTTP errors

  • Normalize error messages

  • Support environment-specific URLs

  • Add logging if needed



Example shape:

export async function apiRequest<T>(

  path: string,

  options: RequestInit = {}

): Promise<T> {

  const response = await fetch(`${API_BASE_URL}${path}`, {

    ...options,

    headers: {

      "Content-Type": "application/json",

      ...getAuthHeaders(),

      ...options.headers,

    },

  });


  if (!response.ok) {

    throw await toApiError(response);

  }


  return response.json() as Promise<T>;

}

This is shared infrastructure.


The wizard should use it, but the wizard should not own all of it.



Step 7: Create the Wizard Feature Service



The wizard feature service is where the form gets clean operations.


For example:

loadWizardForm(id)

saveWizardStep(stepId, formData)

submitWizardForm(formData)

deleteWizardDraft(id)

getReferenceData()

The React form should call these methods.


It should not manually construct raw API payloads.


Example:

export async function loadWizardForm(id: string): Promise<WizardFormModel> {

  const apiResponse = await apiRequest<ApiFormResponse>(`/forms/${id}`);

  return mapApiToWizardForm(apiResponse);

}


export async function saveWizardForm(form: WizardFormModel): Promise<void> {

  const payload = mapWizardFormToApi(form);


  await apiRequest(`/forms/${form.id}`, {

    method: "PUT",

    body: JSON.stringify(payload),

  });

}

That gives the UI a clean contract.


The UI says:


Load the wizard.


The service handles:


How to call the API, map the response, handle errors, and return the right form model.



Step 8: Add Mappers



The mappers are where the translation happens.


One mapper goes from API response to UI form model.


Another goes from UI form model to API request payload.


Example:

export function mapApiToWizardForm(api: ApiFormResponse): WizardFormModel {

  return {

    firstName: api.person?.first_name ?? "",

    lastName: api.person?.last_name ?? "",

    dateOfBirth: api.person?.dob ?? "",

    applicationType: mapApplicationTypeFromApi(api.workflow?.application_type),

    hasPriorEnrollment: api.enrollment?.priorEnrollmentFlag === "Y",

  };

}


export function mapWizardFormToApi(form: WizardFormModel): SaveWizardRequest {

  return {

    person: {

      first_name: form.firstName,

      last_name: form.lastName,

      dob: form.dateOfBirth,

    },

    workflow: {

      application_type: mapApplicationTypeToApi(form.applicationType),

    },

    enrollment: {

      priorEnrollmentFlag: form.hasPriorEnrollment ? "Y" : "N",

    },

  };

}

This is where a lot of teams skip structure.


Do not skip this.


This is the heart of the integration.



Step 9: Add Runtime Validation Where Appropriate



TypeScript helps during development.


Runtime validation helps when the API sends unexpected data.


If the repo already uses something like Zod, Yup, AJV, or another schema library, use the existing pattern.


Example:

const ApiFormResponseSchema = z.object({

  id: z.string(),

  person: z.object({

    first_name: z.string().optional(),

    last_name: z.string().optional(),

    dob: z.string().optional(),

  }).optional(),

});

Then validate API responses before mapping them.


This helps catch contract drift early.


If the backend changes the payload, the frontend should detect it clearly.



Step 10: Preserve the Mock Service as a Fallback



Do not throw away mock data too early.


The backend may be incomplete.


Auth may not work locally.


Some fields may be missing.


Some endpoints may not be ready.


The team still needs to keep building and testing the UI.


Create a real service and a mock service behind the same interface.


Example:

export const wizardService = USE_MOCK_API

  ? mockWizardService

  : realWizardService;

This lets the team switch between mock and real integration.


That is how you protect velocity.



Step 11: Connect One Wizard Step First



Do not connect the entire wizard at once.


Pick one step.


Make that step work end to end.


The first integration goal should be:

Load data

Populate the form

Edit fields

Save

Reload

Confirm data persisted

Once one step works, the pattern can be repeated.


That is where Claude Code becomes powerful.


The first step teaches the repo the pattern.


The remaining steps become controlled repetition.



Step 12: Handle Missing Fields Explicitly



Some fields in the Figma screen may not exist in the API.


Some fields in the API may not exist in the UI.


Some fields may exist but use different names or formats.


Do not hide this.


Document it.


Use the mapping table.


Use comments carefully.


Use an integration gap report.


Example:

Field: middleName

UI: Present

API: Missing

Database: Missing

Current Handling: Stored only in local form state

Backend Question: Should this be added to the API contract?

Do not invent backend fields.


Do not silently drop form fields without telling the team.


Do not fake persistence.


If a field is not supported, make that visible.



Step 13: Add Error Handling That Users Can Understand



API errors should not leak raw technical messages into the UI.


The integration layer should translate errors into useful messages.


Examples:

401 Unauthorized → Your session has expired. Please sign in again.

403 Forbidden → You do not have permission to update this form.

404 Not Found → This form could not be found.

409 Conflict → This form was updated by someone else. Please reload.

422 Validation Error → Some fields need correction before saving.

500 Server Error → Something went wrong while saving. Please try again.

This belongs in the service/error layer, not scattered across every form field.



Step 14: Add Tests for the Integration Layer



At minimum, test the mappers.


The mapper tests are extremely valuable because they prove the field translation.


Test:


  • API response to UI form model

  • UI form model to API request payload

  • Missing optional fields

  • Enum transformations

  • Boolean transformations

  • Date transformations

  • Validation error handling



Example:

Given API response with priorEnrollmentFlag = "Y"

When mapped to the wizard form

Then hasPriorEnrollment should be true

These tests are simple, fast, and highly valuable.



Step 15: Update the Definition of Done



For integrated wizard forms, “done” cannot mean the screen renders.


The definition of done should include:


  • Form renders from real or mock data

  • One or more steps load from API

  • Save operation works

  • Submit operation works if in scope

  • Required API fields are mapped

  • Missing backend fields are documented

  • Business rules still work

  • Validation still works

  • Error messages are handled

  • Mapper tests are passing

  • Feature can still run with mock data

  • Field mapping table is updated

  • Backend questions are documented



That is a real definition of done.



The Claude Code Intent for This Work



Here is the kind of intent file the team should use:

# Intent: Integrate Wizard Form with Backend REST APIs


## Goal


Connect the existing React wizard form to the backend REST APIs through a clean feature integration layer.


Preserve the existing mock data as a fallback while adding real API integration.


## Context


The wizard form was created from Figma screens, Jira story details, acceptance criteria, and mock data.


The backend is API-first and will provide protected REST endpoints for CRUD operations.


Some fields in the UI may not exist in the API or database yet. Some API/database fields may not map perfectly to the UI.


## Required Inputs


Use the following materials:


- Existing React wizard form

- Figma screens

- Jira story

- Acceptance criteria

- Current mock data

- API endpoint documentation

- Sample request payloads

- Sample response payloads

- Error response examples

- Database schema

- Auth instructions

- Known missing fields


## Architecture


Use this flow:


Wizard UI

→ Wizard form state/controller

→ Wizard feature service

→ Shared API client/integration layer

→ Protected REST APIs


Do not call APIs directly from React components.


## Tasks


1. Inspect the repo and find existing API patterns.

2. Find how auth headers and API base URLs are handled.

3. Identify the current wizard form model.

4. Identify the current mock data shape.

5. Identify the provided API request and response shapes.

6. Create a field mapping table:

   - UI Field

   - Mock Field

   - API Field

   - DB Field

   - Status

   - Notes

7. Identify unmapped fields.

8. Identify fields requiring transformation.

9. Create or reuse a shared API client.

10. Create a wizard feature service.

11. Create API request/response types.

12. Create mappers between API and UI models.

13. Add runtime schema validation if the repo already uses a validation library.

14. Preserve mock service behavior through a feature flag or adapter.

15. Connect one wizard step first.

16. Verify load, edit, save, and reload.

17. Add tests for mapper logic.

18. Document assumptions, API gaps, and backend questions.


## Rules


- Do not invent backend fields.

- Do not silently drop UI fields.

- Do not put raw API calls inside React components.

- Do not remove mock data until real API integration is verified.

- Do not connect the entire wizard at once.

- Start with one step and prove the pattern.

- Keep business rules separate from data mapping rules.

- Keep validation rules separate from API mapping rules.


## Final Output


When complete, provide:


1. Files changed

2. Integration architecture summary

3. Field mapping table

4. API gaps

5. Missing backend fields

6. Assumptions made

7. How to run locally

8. How to switch between mock and real API

9. How to test the integration

10. Remaining backend questions


The Pattern the Team Should Repeat



Once the first wizard step works, repeat the same pattern for the remaining steps.


The repeatable pattern is:

Understand the UI fields

Compare against mock data

Compare against API contract

Compare against database schema

Create or update mapping table

Add mapper logic

Add service method

Add validation

Wire the UI step

Test load/save

Document gaps

Move to the next step

That is the playlist.



Final Thought



The problem is usually not that 60 engineers cannot build a form.


The problem is that the team has not turned the integration into a clear engineering pattern.


A complex enterprise form is not just a React screen.


It is a contract between product, design, frontend, backend, database, security, and QA.


Claude Code can help move that work much faster, but only if we give it the right structure.


The winning move is not:


Build the form.


The winning move is:


Build the integration pattern once, prove it on one wizard step, and then repeat it safely across the rest of the form.


That is how teams move from slow feature delivery to repeatable AI-assisted engineering.

:::

 
 
 

Recent Posts

See All

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Post: Blog2_Post

Subscribe Form

Thanks for submitting!

©2020 by LearnTeachMaster DevOps. Proudly created with Wix.com

bottom of page