Skip to content

<ReactForm> - The Root Component

The root component that wires everything together. It manages the entire data lifecycle - state, fields, values, and API calls - and provides context for all child components.

It's the only component you must include. Every other component depends on the context provided by <ReactForm>.

jsx
<ReactForm>
  {({ context }) => (
    // Child Components
  )}
</ReactForm>

Props

itemId

  • Type: Number|String

The identifier passed to isNewItemCheck for mode detection. Use special strings like '+' or 'create' to indicate create mode, or pass the database ID for update mode.

jsx
// Create mode
<ReactForm itemId="create" />

// Update mode
<ReactForm itemId={7} />
<ReactForm itemId="abc-123" />

// From route params
<ReactForm itemId={params.id} />

See how to use itemId in isNewItemCheck here

create

  • Type: Function
  • Signature: (context) => Promise<ReactFormApiResponse>

Async function called when the create button is clicked. Receives the full context object and must return a promise resolving to a ReactFormApiResponse.

jsx
function create(context) {
  return fetch("/api/users/").then((res) => {
    return {
      values: { ... }, // {key:value} pairs for each field
    };
  });
}

Important

Do not catch errors in your create, read, update, delete, archive, or unarchive methods. Read more

read

  • Type: Function
  • Signature: (itemId, context) => Promise<ReactFormApiResponse>

Async function called when the component is mounted and update mode is detected. Must return a promise resolving to a ReactFormApiResponse.

jsx
function read(itemId, context) {
  return fetch(`/api/users/${itemId}`).then((res) => {
    return {
      values: { ... }, // {key:value} pairs for each field
    };
  });
}

update

  • Type: Function
  • Signature: (itemId, context) => Promise<ReactFormApiResponse>

Async function called when the update button is clicked. Must return a promise resolving to a ReactFormApiResponse.

jsx
function update(itemId, context) {
  return fetch(`/api/users/${itemId}`, {
    method: "PUT",
    body: JSON.stringify(context.values),
  }).then((res) => {
    return {
      values: { ... }, // {key:value} pairs for each field
    };
  });
}

delete

  • Type: Function
  • Signature: (itemId, context) => Promise<ReactFormApiResponse>

Async function called when the delete button is clicked. Must return a promise resolving to a ReactFormApiResponse.

jsx
function deleteItem(itemId, context) {
  return fetch(`/api/users/${itemId}`, {
    method: "DELETE",
  }).then((res) => {
    return {};
  });
}

archive

  • Type: Function
  • Signature: (itemId, context) => Promise<ReactFormApiResponse>

Async function called when the archive button is clicked. Must return a promise resolving to a ReactFormApiResponse.

jsx
function archive(itemId, context) {
  return fetch(`/api/users/${itemId}/archive`, {
    method: "POST",
  }).then((res) => {
    return {
      values: { ... },
      isArchived: true,
    };
  });
}

unarchive

  • Type: Function
  • Signature: (itemId, context) => Promise<ReactFormApiResponse>

Async function called when the unarchive button is clicked. Must return a promise resolving to a ReactFormApiResponse.

jsx
function unarchive(itemId, context) {
  return fetch(`/api/users/${itemId}/unarchive`, {
    method: "POST",
  }).then((res) => {
    return {
      values: { ... },
      isArchived: false,
    };
  });
}

isNewItemCheck

  • Type: Function
  • Optional

Custom function to determine if item is new (overrides provider). See Configurations for more details.

isArchivedItemCheck

  • Type: Function
  • Optional

Custom function to check if item is archived (overrides provider). See Configurations for more details.

errorAdapter

  • Type: Function
  • Optional

Custom error adapter function (overrides provider). See Configurations for more details.

componentPrefix

  • Type: String
  • Optional

Component prefix (overrides provider). Not used in React (kept for consistency with React version).

className

  • Type: String
  • Optional
  • Default: "react-form"

CSS class name for the form container.

schema

  • Type: Any
  • Optional

Validation schema (Zod, Yup, or custom) - passed to validateSchema function. When provided, the form will automatically validate before submitting.

With Zod
jsx
import { z } from "zod";

const schema = z.object({
  name: z
    .string()
    .min(2, "Name must be at least 2 characters")
    .default("") // Must Pass default for validate schema properly
    .describe("Name"), // Customize Label
  email: z
    .string()
    .email("Invalid email address")
    .default("") // Must Pass default for validate schema properly
    .describe("Email"), // Customize Label
});

<ReactForm schema={schema} {...otherProps} />;
With Yup
jsx
import * as yup from "yup";

const schema = yup.object({
  name: yup
    .string()
    .min(2, "Name must be at least 2 characters")
    .default("") // Must Pass default for validate schema properly
    .label("Name"), // Customize Label
  email: yup
    .string()
    .email("Invalid email address")
    .default("") // Must Pass default for validate schema properly
    .label("Email"), // Customize Label
});

<ReactForm schema={schema} {...otherProps} />;

schemaToFields

  • Type: Function
  • Optional

Custom function to extract fields from schema (overrides provider). This function is called when a schema prop is provided.

With Zod
jsx
const schemaToFields = (schema) => {
  return Object.keys(schema.shape).map((key) => {
    const zodField = schema.shape[key];
    /* 
      - "defaultValue" & "description" key can be vary or not found 
      with different zod version (current - v4)
      - check it with console.log("zodField:", zodField);  
      - Need to map the keys accordingly
      */
    const defaultValue = zodField.def.defaultValue;
    const label = zodField.description;

    return {
      name: key,
      label: label || key.charAt(0).toUpperCase() + key.slice(1),
      value: defaultValue,
    };
  });
};

<ReactForm schema={schema} schemaToFields={schemaToFields} {...otherProps} />;
With Yup
jsx
const schemaToFields = (schema) => {
  return Object.keys(schema.fields).map((key) => {
    const yupField = schema.fields[key];
    /* 
      - ".spec.default" & ".spec.label" key can be vary or not found 
      with different yup version (current - 1.7.1)
      - check it with console.log("yupField:", yupField);  
      - Need to map the keys accordingly
      */
    const label = yupField.spec.label;
    const defaultValue = yupField.spec.default;
    return {
      name: key,
      label: label || key.charAt(0).toUpperCase() + key.slice(1),
      value: defaultValue,
    };
  });
};

<ReactForm schema={schema} schemaToFields={schemaToFields} {...otherProps} />;

validateSchema

  • Type: Function
  • Optional

Custom validation function (overrides provider). This function is called automatically when create or update is triggered.

Signature: (schema, values) => Promise<{ success: boolean, errors: Object }>

With Zod
jsx
const validateSchema = async (schema, values) => {
  const result = schema.safeParse(values);

  if (result.success) {
    return { success: true, errors: {} };
  }

  const fieldErrors = result.error.issues.reduce((acc, issue) => {
    const field = issue.path[0];
    if (!field) return acc;
    acc[field] = { message: issue.message };
    return acc;
  }, {});

  return { success: false, errors: fieldErrors };
};

<ReactForm schema={schema} validateSchema={validateSchema} {...otherProps} />;
With Yup
jsx
const validateSchema = async (schema, values) => {
  try {
    await schema.validate(values, { abortEarly: false });
    return { success: true, errors: {} };
  } catch (error) {
    const fieldErrors = {};

    if (error.inner) {
      error.inner.forEach((err) => {
        if (err.path) {
          fieldErrors[err.path] = { message: err.message };
        }
      });
    }

    return { success: false, errors: fieldErrors };
  }
};

<ReactForm schema={schema} validateSchema={validateSchema} {...otherProps} />;

Complete Example For Local Configuration

With Zod
jsx
import { ReactForm, ReactFormField } from "@7span/react-form";
import { z } from "zod";

const schema = z.object({
  name: z
    .string()
    .min(2, "Name must be at least 2 characters")
    .default("") // Must Pass default for validate schema properly
    .describe("Name"), // Customize Label
  email: z
    .string()
    .email("Invalid email address")
    .default("") // Must Pass default for validate schema properly
    .describe("Email"), // Customize Label
});

// Local schemaToFields (overrides provider)
const schemaToFields = (schema) => {
  return Object.keys(schema.shape).map((key) => {
    const zodField = schema.shape[key];
    /* 
      - "defaultValue" & "description" key can be vary or not found 
      with different zod version (current - v4)
      - check it with console.log("zodField:", zodField);  
      - Need to map the keys accordingly
      */
    const defaultValue = zodField.def.defaultValue;
    const label = zodField.description;

    return {
      name: key,
      label: label || key.charAt(0).toUpperCase() + key.slice(1),
      value: defaultValue,
    };
  });
};

// Local validateSchema (overrides provider)
const validateSchema = async (schema, values) => {
  const result = schema.safeParse(values);

  if (result.success) {
    return { success: true, errors: {} };
  }

  const fieldErrors = result.error.issues.reduce((acc, issue) => {
    const field = issue.path[0];
    if (!field) return acc;
    acc[field] = { message: issue.message };
    return acc;
  }, {});

  return { success: false, errors: fieldErrors };
};

function MyForm() {
  return (
    <ReactForm
      schema={schema}
      schemaToFields={schemaToFields}
      validateSchema={validateSchema}
      create={create}
      update={update}
    >
      {({ context }) => (
        <>
          <ReactFormField name="name">
            {({ field, label, error }) => (
              <>
                <label>{label}</label>
                <input {...field} />
                {error && <span>{error.message}</span>}
              </>
            )}
          </ReactFormField>
          <ReactFormField name="email">
            {({ field, label, error }) => (
              <>
                <label>{label}</label>
                <input type="email" {...field} />
                {error && <span>{error.message}</span>}
              </>
            )}
          </ReactFormField>
        </>
      )}
    </ReactForm>
  );
}
With Yup
jsx
import { ReactForm, ReactFormField } from "@7span/react-form";
import * as Yup from "yup";

const schema = yup.object({
  name: yup
    .string()
    .min(2, "Name must be at least 2 characters")
    .default("") // Must Pass default for validate schema properly
    .label("Name"), // Customize Label
  email: yup
    .string()
    .email("Invalid email address")
    .default("") // Must Pass default for validate schema properly
    .label("Email"), // Customize Label
});

// Local schemaToFields (overrides provider)
const schemaToFields = (schema) => {
  return Object.keys(schema.fields).map((key) => {
    const yupField = schema.fields[key];
    /* 
      - ".spec.default" & ".spec.label" key can be vary or not found 
      with different yup version (current - 1.7.1)
      - check it with console.log("yupField:", yupField);  
      - Need to map the keys accordingly
      */
    const label = yupField.spec.label;
    const defaultValue = yupField.spec.default;
    return {
      name: key,
      label: label || key.charAt(0).toUpperCase() + key.slice(1),
      value: defaultValue,
    };
  });
};

// Local validateSchema (overrides provider)
const validateSchema = async (schema, values) => {
  try {
    await schema.validate(values, { abortEarly: false });
    return { success: true, errors: {} };
  } catch (error) {
    const fieldErrors = {};

    if (error.inner) {
      error.inner.forEach((err) => {
        if (err.path) {
          fieldErrors[err.path] = { message: err.message };
        }
      });
    }

    return { success: false, errors: fieldErrors };
  }
};

function MyForm() {
  return (
    <ReactForm
      schema={schema}
      schemaToFields={schemaToFields}
      validateSchema={validateSchema}
      create={create}
      update={update}
    >
      {({ context }) => (
        <>
          <ReactFormField name="name">
            {({ field, label, error }) => (
              <>
                <label>{label}</label>
                <input {...field} />
                {error && <span>{error.message}</span>}
              </>
            )}
          </ReactFormField>
          <ReactFormField name="email">
            {({ field, label, error }) => (
              <>
                <label>{label}</label>
                <input type="email" {...field} />
                {error && <span>{error.message}</span>}
              </>
            )}
          </ReactFormField>
        </>
      )}
    </ReactForm>
  );
}

Render Props

context

The only render prop available in <ReactForm> is context. It exposes a set of variables that you can access.

Context Properties

KeyDescription
contextObject
The context object
readItemFunction
Wrapper of read function. You can use this method to manually do the read call
updateItemFunction
Wrapper of update function. You can use this instead of <ReactFormUpdate>
deleteItemFunction
Wrapper of delete function. You can use this instead of <ReactFormDelete>
archiveItemFunction
Wrapper of archive function. You can use this instead of <ReactFormArchive>
unarchiveItemFunction
Wrapper of unarchive function. You can use this instead of <ReactFormUnarchive>
setValueFunction
Manually set a field value
valuesObject
Current form values
dirtyValuesObject
Only fields that have been modified
errorsObject
Field-level errors
errorObject
Form-level error
isLoadingBoolean
True when any async operation is in progress
isNewItemBoolean
True when in create mode
isArchivedBoolean
True if item is archived
isReadingBoolean
True while reading
isCreatingBoolean
True while creating
isUpdatingBoolean
True while updating
isDeletingBoolean
True while deleting
isArchivingBoolean
True while archiving
isUnarchivingBoolean
True while unarchiving

See the Context Object documentation for a complete reference.

Version 1.0.0