diff --git a/docs/hooks/hooks.md b/docs/hooks/hooks.md index 99eba239b..f4e43122b 100644 --- a/docs/hooks/hooks.md +++ b/docs/hooks/hooks.md @@ -96,11 +96,20 @@ Wrap fields in `SDKFormProvider` and they pick up form state from context automa ```tsx import { SDKFormProvider } from '@gusto/embedded-react-sdk' -; - - - - + +function EmployeeFormSection() { + // ...employeeDetails from useEmployeeDetailsForm + + const { Fields } = employeeDetails.form + + return ( + + + + + + ) +} ``` Fields inside an `SDKFormProvider` don't need the `formHookResult` prop — the provider injects form state via React context. This is convenient when all fields from a single hook are grouped together. @@ -116,7 +125,35 @@ Both approaches produce identical validation, API payloads, and behavior. The di | **Interleaving** | Fields from different hooks can be placed in any order | Fields must stay within their provider boundary | | **API error syncing** | Handled automatically per-field | Handled automatically via the provider | -You can use different approaches for different hooks on the same page — for example, `SDKFormProvider` for one hook's fields that are grouped together, and `formHookResult` props for another hook's fields that are scattered. See [Composing Multiple Hooks](#composing-multiple-hooks) for examples. +#### Side-by-side: two hooks on one screen + +The same `employeeDetails` + `compensation` form, written each way: + +```tsx +// Option A: formHookResult prop — fields can be interleaved freely +const EmployeeDetailsFields = employeeDetails.form.Fields +const CompensationFields = compensation.form.Fields + + + + + +``` + +```tsx +// Option B: SDKFormProvider — one wrapper per hook, fields stay grouped + + + + + + + + + +``` + +You can also mix approaches on the same page — for example, `SDKFormProvider` for one hook's fields that are grouped together, and `formHookResult` props for another hook's fields that are scattered. See [Composing Multiple Hooks](#composing-multiple-hooks) for the full submit-handler wiring around either layout. > **Avoid passing `formHookResult` to fields via props that are already inside an `SDKFormProvider`.** When both are present on the same field, the prop takes precedence and the provider's context is ignored, which may lead to unexpected behavior. @@ -155,6 +192,7 @@ function MyCustomTextInput(props: TextInputProps) { ) } +// Then in your form: ; ``` -If you omit `validationMessages`, validation still runs but no message is displayed — the field is marked as invalid without explanatory text. +If you omit `validationMessages`, validation still runs and the field is marked as invalid, but the displayed text falls back to the raw error code (e.g., `REQUIRED`, `INVALID_EMAIL`, `INVALID_AMOUNT`). Always supply `validationMessages` for production UI so partners control the user-facing copy. Error codes for each hook are exported alongside the hook: @@ -847,19 +885,3 @@ Use this when you need to: - Access form state like `isDirty`, `isValid`, or `dirtyFields` In most cases the built-in Fields, `onSubmit`, and `getFormSubmissionValues` are sufficient. Reach for `hookFormInternals` only when you need fine-grained form control that the hook doesn't expose directly. - ---- - -## Advanced: Fields Metadata - -Each hook exposes `form.fieldsMetadata` — an object keyed by field name with metadata about each field's current state. The field components consume this automatically under the hood to determine required/disabled states and populate select options, so you typically don't need to interact with it directly. - -If you're building fully custom field UI, you can read this metadata yourself: - -```tsx -const { fieldsMetadata } = employeeDetails.form - -if (fieldsMetadata.email.isRequired) { - // Show a required indicator in your custom UI -} -``` diff --git a/docs/hooks/useCompensationForm.md b/docs/hooks/useCompensationForm.md index 6f53efe7f..775ca801b 100644 --- a/docs/hooks/useCompensationForm.md +++ b/docs/hooks/useCompensationForm.md @@ -29,35 +29,35 @@ import { useCompensationForm, SDKFormProvider } from '@gusto/embedded-react-sdk' ### Configurable Required Fields -The compensation schema declares requiredness rules per field. Fields default to **always required** unless configured otherwise: - -| Field | Rule | Required on create | Required on update | Partner-configurable? | -| ----------------------- | ---------- | ------------------ | ------------------ | --------------------- | -| `jobTitle` | `'create'` | Yes | No | Yes (on update) | -| `flsaStatus` | `'create'` | Yes | No | Yes (on update) | -| `paymentUnit` | `'create'` | Yes | No | Yes (on update) | -| `rate` | `'create'` | Yes | No | Yes (on update) | -| `startDate` | `'create'` | Yes | No | Yes (on update) | -| `adjustForMinimumWage` | (unlisted) | Yes | Yes | No | -| `stateWcCovered` | (unlisted) | Yes | Yes | No | -| `twoPercentShareholder` | (unlisted) | Yes | Yes | No | -| `minimumWageId` | predicate | When toggle is on | When toggle is on | No | -| `stateWcClassCode` | predicate | When WC is covered | When WC is covered | No | - -`optionalFieldsToRequire` lets you override fields that are **optional** in a given mode to be required. The type is derived from the schema configuration: +Each compensation field has a default required-ness based on form mode (create vs. update). Fields default to **always required** unless configured otherwise: + +| Field | Required on create | Required on update | Partner-configurable? | +| ----------------------- | ------------------ | ------------------ | --------------------- | +| `jobTitle` | Yes | No | Yes (on update) | +| `flsaStatus` | Yes | No | Yes (on update) | +| `paymentUnit` | Yes | No | Yes (on update) | +| `rate` | Yes | No | Yes (on update) | +| `startDate` | Yes | No | Yes (on update) | +| `adjustForMinimumWage` | Yes | Yes | No | +| `stateWcCovered` | Yes | Yes | No | +| `twoPercentShareholder` | Yes | Yes | No | +| `minimumWageId` | When toggle is on | When toggle is on | No | +| `stateWcClassCode` | When WC is covered | When WC is covered | No | + +`optionalFieldsToRequire` lets you override fields that are **optional** in a given mode to be required. TypeScript constrains which fields can be promoted per mode: ```typescript type CompensationOptionalFieldsToRequire = { - create?: never[] // all 'create' fields are already required on create + create?: never[] // these fields are already required on create update?: Array<'jobTitle' | 'flsaStatus' | 'paymentUnit' | 'rate' | 'startDate'> } ``` -Only the `update` key is useful for compensation — it lets you require fields in update mode that would otherwise be optional. The `create` key has no configurable fields because all `'create'`-scoped fields are already required on create. +Only the `update` key is useful for compensation — it lets you require fields in update mode that would otherwise be optional. The `create` key has no configurable fields because every field listed above is already required on create. -`startDate` requirements are ignored when `withStartDateField` is `false` (the field is excluded from the schema entirely). +`startDate` requirements are ignored when `withStartDateField` is `false` (the field is omitted from the form entirely). -Cross-field validations (`minimumWageId` when `adjustForMinimumWage` is `true`, workers' comp fields in WA) are always enforced by the schema regardless of `optionalFieldsToRequire`. +Cross-field rules (`minimumWageId` when `adjustForMinimumWage` is `true`, workers' comp fields in WA) are always enforced regardless of `optionalFieldsToRequire`. ```tsx useCompensationForm({ @@ -312,7 +312,7 @@ This field is automatically **disabled** when the FLSA status is Commission Only validationMessages={{ REQUIRED: 'Amount is a required field', RATE_MINIMUM: 'Amount must be at least $1.00', - RATE_EXEMPT_THRESHOLD: `FLSA Exempt employees must meet salary threshold of $${FLSA_OVERTIME_SALARY_LIMIT}/year`, + RATE_EXEMPT_THRESHOLD: 'FLSA Exempt employees must meet salary threshold of $35,568/year', }} /> ``` diff --git a/docs/hooks/useEmployeeDetailsForm.md b/docs/hooks/useEmployeeDetailsForm.md index 4ceb7f6e6..62cb2fce7 100644 --- a/docs/hooks/useEmployeeDetailsForm.md +++ b/docs/hooks/useEmployeeDetailsForm.md @@ -158,7 +158,7 @@ const EmployeeDetailsErrorCodes = { ### Fields.FirstName -Text input for the employee's first name. Validates against `NAME_REGEX` to reject special characters. +Text input for the employee's first name. Validates that the value contains only allowed name characters (letters, spaces, hyphens, and apostrophes). | Prop | Type | Required | | -------------------- | -------------------------------------------- | -------- | @@ -208,7 +208,7 @@ No validation codes — this field is always optional. ### Fields.LastName -Text input for the employee's last name. Validates against `NAME_REGEX` to reject special characters. +Text input for the employee's last name. Validates that the value contains only allowed name characters (letters, spaces, hyphens, and apostrophes). | Prop | Type | Required | | -------------------- | -------------------------------------------- | -------- | @@ -310,7 +310,7 @@ Text input for the employee's Social Security number. Automatically formats inpu | ------------- | -------------------------------------------- | | `INVALID_SSN` | Value does not match the expected SSN format | -The `fieldsMetadata.ssn.hasRedactedValue` flag indicates whether the employee already has an SSN on file. When `true`, the field renders with a masked placeholder (e.g., `•••-••-1234`). If the field is included in `requiredFields` but `hasSsn` is already `true` on the employee, the requirement is automatically waived. +The masked placeholder appears as `•••-••-1234`. When the employee already has an SSN on record, the `REQUIRED` rule is automatically waived even if `ssn` is listed in `requiredFields`. ```tsx ` | No | — | Pre-fill form values. Server data takes precedence when editing an existing work address. | -| `validationMode` | `'onSubmit' \| 'onBlur' \| 'onChange' \| 'onTouched' \| 'all'` | No | `'onSubmit'` | When validation runs. Passed through to react-hook-form. | -| `shouldFocusError` | `boolean` | No | `true` | Auto-focus the first invalid field on submit. Set to `false` when using `composeSubmitHandler`. | +| Prop | Type | Required | Default | Description | +| ------------------------ | ------------------------------------------------------------------------------------ | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `companyId` | `string` | Yes | — | The UUID of the company. Used to fetch available locations. | +| `employeeId` | `string` | Yes | — | The UUID of the employee. For composed create flows where the id isn't known until a prior form submits, pass `''` here and supply the real id at submit time via `WorkAddressSubmitOptions.employeeId`. | +| `workAddressUuid` | `string` | No | — | When set, the hook is in update mode and loads that work address row. When omitted, the hook is in create mode. Use `useCurrentWorkAddressForm` to auto-resolve the employee's active row. | +| `withEffectiveDateField` | `boolean` | No | `true` | Whether to include the effective date field. When `false`, pass effective date via `onSubmit` options instead. | +| `requiredFields` | `WorkAddressField[] \| { create?: WorkAddressField[], update?: WorkAddressField[] }` | No | — | Additional fields to make required beyond API defaults. A flat array applies to both modes; an object targets specific modes. | +| `defaultValues` | `Partial` | No | — | Pre-fill form values. Server data takes precedence when editing an existing work address. | +| `validationMode` | `'onSubmit' \| 'onBlur' \| 'onChange' \| 'onTouched' \| 'all'` | No | `'onSubmit'` | When validation runs. Passed through to react-hook-form. | +| `shouldFocusError` | `boolean` | No | `true` | Auto-focus the first invalid field on submit. Set to `false` when using `composeSubmitHandler`. | ### WorkAddressField @@ -108,7 +108,7 @@ The hook returns a discriminated union on `isLoading`. ### Mode detection -The hook is in update mode when `workAddressUuid` is provided (the row is fetched via GET `/v1/work_addresses/{work_address_uuid}`) and in create mode otherwise. +The hook is in update mode when `workAddressUuid` is provided and in create mode otherwise. ### Submit callbacks