Skip to main content

useEmployeeStateTaxesForm

useEmployeeStateTaxesForm(props: UseEmployeeStateTaxesFormProps): UseEmployeeStateTaxesFormResult

Headless form hook for updating an employee's state tax withholding answers. The set of questions is driven by the API response per state, so form.Fields is an array of state groups with discriminated, render-ready Field components rather than a fixed named object.

Remarks

The state-tax record(s) are created automatically with the employee, so this hook is always in update mode. When the form has no states with submittable answers (e.g. an employee in a no-income-tax state), submit resolves with the existing record list without making a network request.

Example

Example
import {
useEmployeeStateTaxesForm,
SDKFormProvider,
type UseEmployeeStateTaxesFormReady,
} from '@gusto/embedded-react-sdk'

function StateTaxesPage({ employeeId }: { employeeId: string }) {
const stateTaxes = useEmployeeStateTaxesForm({ employeeId })

if (stateTaxes.isLoading) return <div>Loading...</div>

return <StateTaxesFormReady stateTaxes={stateTaxes} />
}

function StateTaxesFormReady({
stateTaxes,
}: {
stateTaxes: UseEmployeeStateTaxesFormReady
}) {
const handleSubmit = async () => {
const result = await stateTaxes.actions.onSubmit()
if (result) console.log('Updated state tax records:', result.data)
}

return (
<SDKFormProvider formHookResult={stateTaxes}>
<form
onSubmit={e => {
e.preventDefault()
void handleSubmit()
}}
>
{stateTaxes.form.Fields.map(group => (
<section key={group.state}>
<h2>{group.state}</h2>
{group.questions.map(question => (
<question.Field key={question.questionId} />
))}
</section>
))}
<button type="submit" disabled={stateTaxes.status.isPending}>
Save
</button>
</form>
</SDKFormProvider>
)
}

Props

UseEmployeeStateTaxesFormProps

Options accepted by useEmployeeStateTaxesForm.

PropertyTypeDescription
employeeIdstringThe UUID of the employee whose state taxes are being updated.
isAdmin?booleanWhen true, admin-only questions are visible and submitted. When false, they are filtered out and the surfaced answer for those questions is preserved unchanged on submit.
shouldFocusError?booleanAuto-focus the first invalid field on submit. Defaults to true. Set to false when composing with other forms.
validationMode?"onChange" | "onBlur" | "onSubmit" | "onTouched" | "all"When validation runs. Passed through to react-hook-form. Defaults to 'onSubmit'.

Returns

UseEmployeeStateTaxesFormResult

A loading result while data is fetching, or a ready result with form data, fields, status, actions, and error handling.

UseEmployeeStateTaxesFormResult

UseEmployeeStateTaxesFormResult = HookLoadingResult | UseEmployeeStateTaxesFormReady

Discriminated union returned by useEmployeeStateTaxesForm. Loading branch carries only errorHandling; ready branch carries form data, fields, status, and actions.


UseEmployeeStateTaxesFormReady

Ready-state return value of useEmployeeStateTaxesForm — the isLoading: false branch of UseEmployeeStateTaxesFormResult.

PropertyTypeDescription
actionsobjectForm actions.
actions.onSubmit() => Promise<HookSubmitResult<EmployeeStateTaxesList[]> | undefined>Validates and submits the form, resolving to the updated records on success or undefined when validation blocked the submit.
dataobjectCurrent per-state tax records returned by the server.
data.employeeStateTaxesEmployeeStateTaxesList[]-
errorHandlingHookErrorHandlingError state and recovery actions.
formobjectForm bindings: pre-bound field components, per-field metadata, submission values, and react-hook-form internals.
form.FieldsStateTaxFields-
form.fieldsMetadataEmployeeStateTaxesFieldsMetadata-
form.getFormSubmissionValues() => EmployeeStateTaxesFormData | undefined-
form.hookFormInternalsHookFormInternals<EmployeeStateTaxesFormData>-
isLoadingfalseAlways false in this branch; discriminates from HookLoadingResult.
statusobjectSubmission status. mode is always 'update' since state-tax records are created with the employee.
status.isPendingboolean-
status.mode"update"-

Fields

StateTaxFields

StateTaxFields = StateTaxFieldsGroup[]

Iterable, render-ready group + question entries with bound Field components, grouped by state.

Example

The value exposed on form.Fields: one entry per state, each carrying its questions as pre-bound Field components you render directly.

function StateTaxQuestions({ fields }: { fields: StateTaxFields }) {
return fields.map(group => (
<section key={group.state}>
<h3>{group.state}</h3>
{group.questions.map(question => (
<question.Field key={question.questionId} />
))}
</section>
))
}

StateTaxFieldsGroup

Group of state-tax questions for a single jurisdiction returned by useStateFields.

Properties

PropertyTypeDescription
questionsStateTaxQuestionFieldEntry[]Ordered list of question entries for this state, post admin-only filtering.
statestringTwo-letter state code.

StateTaxQuestionFieldEntry

StateTaxQuestionFieldEntry = SelectStateTaxQuestion | RadioStateTaxQuestion | TextStateTaxQuestion | NumberStateTaxQuestion | CurrencyStateTaxQuestion | DateStateTaxQuestion

One question entry within a StateTaxFieldsGroup, discriminated by type to identify which input variant the question uses. Each entry carries a Field component pre-bound to its API-supplied metadata so callers can render the input directly.


CurrencyStateTaxQuestion

A state-tax question that renders as a currency-formatted number input. Includes read-only question metadata from the API and a bound currency field, exposed as <question.Field />.

Properties

PropertyTypeDescription
FieldComponentType<CurrencyStateTaxFieldProps>Field component pre-bound to this question's API-supplied metadata.
type"currency"Discriminant identifying the currency variant.

Also includes description, label, questionId from SharedQuestionMetadata.


DateStateTaxQuestion

A state-tax question that renders as a date picker. Includes read-only question metadata from the API and a bound date field, exposed as <question.Field />.

Properties

PropertyTypeDescription
FieldComponentType<DateStateTaxFieldProps>Field component pre-bound to this question's API-supplied metadata.
type"date"Discriminant identifying the date variant.

Also includes description, label, questionId from SharedQuestionMetadata.


NumberStateTaxQuestion

A state-tax question that renders as a decimal number input. Includes read-only question metadata from the API and a bound number field, exposed as <question.Field />.

Properties

PropertyTypeDescription
FieldComponentType<NumberStateTaxFieldProps>Field component pre-bound to this question's API-supplied metadata.
type"number"Discriminant identifying the number variant.

Also includes description, label, questionId from SharedQuestionMetadata.


RadioStateTaxQuestion

A state-tax question that renders as a radio group. Includes read-only question metadata from the API and a bound radio field, exposed as <question.Field />.

Properties

PropertyTypeDescription
FieldComponentType<RadioStateTaxFieldProps>Field component pre-bound to this question's API-supplied metadata.
type"radio"Discriminant identifying the radio variant.

Also includes description, label, questionId from SharedQuestionMetadata.


SelectStateTaxQuestion

A state-tax question that renders as a select (dropdown). Includes read-only question metadata from the API and a bound select field, exposed as <question.Field />.

Properties

PropertyTypeDescription
FieldComponentType<SelectStateTaxFieldProps>Field component pre-bound to this question's API-supplied metadata.
type"select"Discriminant identifying the select variant.

Also includes description, label, questionId from SharedQuestionMetadata.


TextStateTaxQuestion

A state-tax question that renders as a single-line text input. Includes read-only question metadata from the API and a bound text field, exposed as <question.Field />.

Properties

PropertyTypeDescription
FieldComponentType<TextStateTaxFieldProps>Field component pre-bound to this question's API-supplied metadata.
type"text"Discriminant identifying the text variant.

Also includes description, label, questionId from SharedQuestionMetadata.

Utility hooks

useStateFields()

useStateFields(employeeStateTaxes: EmployeeStateTaxesList[], isAdmin: boolean): StateTaxFieldsGroup[]

Memoizes the bound field components for a state-taxes form, avoiding unnecessary rebuilds when the data refetches but the underlying questions haven't changed.

Parameters

ParameterTypeDescription
employeeStateTaxesEmployeeStateTaxesList[]Array of state-tax groups returned by the employee state-taxes API.
isAdminbooleanWhen true, admin-only questions are included; when false, they are filtered out.

Returns

StateTaxFieldsGroup[]

An array of StateTaxFieldsGroup — one entry per state, each with a questions array of bound field components.

Utility types

BaseStateTaxFieldProps

Props shared by every state-tax Field variant. Each variant extends this with a variant-specific FieldComponent shape; select and text also add a placeholder.

Extended by

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

CurrencyStateTaxFieldProps

Props for an API-supplied state-tax question rendered as a currency-formatted number input.

Override the user-visible text for this field — its label, description, and validation messages.

Extends

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
FieldComponent?ComponentType<NumberInputProps>Replace the underlying SDK NumberInput primitive with a component of your own.
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

DateStateTaxFieldProps

Props for an API-supplied state-tax question rendered as a date picker.

Override the user-visible text for this field — its label, description, and validation messages.

Extends

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
FieldComponent?ComponentType<DatePickerProps>Replace the underlying SDK DatePicker primitive with a component of your own.
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

EmployeeStateTaxesErrorCode

EmployeeStateTaxesErrorCode = "REQUIRED"

Union of validation error code strings emitted by the employee state taxes form schema.


EmployeeStateTaxesErrorCodes

const EmployeeStateTaxesErrorCodes: object

Validation error codes produced by the useEmployeeStateTaxesForm schema.

Remarks

Use these constants as the keys in a field's validationMessages prop to map an error code to a user-facing message. The state-taxes form surfaces only a single error code: every required field that is empty emits REQUIRED.

Type Declaration

NameType
REQUIRED"REQUIRED"

EmployeeStateTaxesFieldsMetadata

EmployeeStateTaxesFieldsMetadata = Record<`states.${string}.${string}`, FieldMetadata | FieldMetadataWithOptions>

Field metadata for useEmployeeStateTaxesForm, keyed by full form path.

Remarks

The set of keys is determined at runtime: one entry per state tax question, keyed as states.<STATE>.<camelCaseQuestionKey>. Each entry is a FieldMetadata, or a FieldMetadataWithOptions for questions the API exposes as a select or radio. Both the questions and their options are driven by the API response per state, so neither the keys nor which entries carry options are known ahead of time.


EmployeeStateTaxesFormData

Shape of the values managed by the employee state taxes form. Keyed by two-letter state code, then by question key (camelCased from the API key).

Properties

PropertyTypeDescription
statesRecord<string, Record<string, StateTaxValue>>Per-state answer map: state code → (camelCased question key → value).

EmployeeStateTaxesFormFields

EmployeeStateTaxesFormFields = UseEmployeeStateTaxesFormReady["form"]["Fields"]

The array of per-state field groups exposed by useEmployeeStateTaxesForm on form.Fields.


NumberStateTaxFieldProps

Props for an API-supplied state-tax question rendered as a decimal number input.

Override the user-visible text for this field — its label, description, and validation messages.

Extends

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
FieldComponent?ComponentType<NumberInputProps>Replace the underlying SDK NumberInput primitive with a component of your own.
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

RadioStateTaxFieldProps

Props for an API-supplied state-tax question rendered as a radio group.

Override the user-visible text for this field — its label, description, and validation messages.

Extends

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
FieldComponent?ComponentType<RadioGroupProps>Replace the underlying SDK RadioGroup primitive with a component of your own.
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

SelectStateTaxFieldProps

Props for an API-supplied state-tax question rendered as a select (dropdown).

Override the user-visible text for this field — its label, description, placeholder, and validation messages.

Extends

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
FieldComponent?ComponentType<SelectProps>Replace the underlying SDK Select primitive with a component of your own.
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
placeholder?stringPlaceholder shown when no option is selected. Defaults to a generic localized string when omitted.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

SharedQuestionMetadata

Metadata shared by every StateTaxQuestionFieldEntry variant, independent of which input the question renders.

Extended by

Properties

PropertyTypeDescription
descriptionstring | nullAPI-supplied description (raw HTML, sanitized internally before render).
labelstringAPI-supplied label; default text for the rendered Field.
questionIdstringStable identifier for this question (camelCase form of the API key).

StateTaxQuestionVariant

StateTaxQuestionVariant = "select" | "radio" | "text" | "number" | "currency" | "date"

UI input variant for a state-tax question — determines which field type renders for a given question from the employee state-taxes API.


StateTaxValidationMessages

StateTaxValidationMessages = ValidationMessages<typeof EmployeeStateTaxesErrorCodes.REQUIRED>

Localized validation messages supported by the state-tax field components. Every variant surfaces a single error code, REQUIRED.


StateTaxValue

StateTaxValue = string | number | boolean | Date | null | undefined

Union of value types a single state-tax answer may hold in the form. The shape depends on the API-provided question variant.


TextStateTaxFieldProps

Props for an API-supplied state-tax question rendered as a single-line text input.

Override the user-visible text for this field — its label, description, placeholder, and validation messages.

Extends

Properties

PropertyTypeDescription
description?ReactNodeOverrides the API-supplied description. When omitted, falls back to question.description (sanitized internally by the underlying field via DOMPurify).
FieldComponent?ComponentType<TextInputProps>Replace the underlying SDK TextInput primitive with a component of your own.
formHookResult?FormHookResultWhen using the hook outside an SDKFormProvider, pass the form-hook result here so the field can connect to it.
label?stringOverrides the API-supplied label. When omitted, falls back to question.label.
placeholder?stringPlaceholder shown when the field is empty.
validationMessages?StateTaxValidationMessagesOverride the default localized validation message(s).

Advanced

Field variants and promotion

The hook resolves each question's UI variant from the API's inputQuestionFormat.type:

API typeVariantNotes
SelectselectRenders as a dropdown with API-supplied options.
NumbernumberDecimal number input.
CurrencycurrencyCurrency-formatted number input.
TexttextSingle-line text input.
DatedateDate picker.
(unknown)textDefensive fall-through for unrecognized wire types.

Two per-key rules override the variant mapping:

  • file_new_hire_report arrives over the wire as Select but is re-promoted to radio so it renders as a radio group.
  • Once an answer to file_new_hire_report has been recorded server-side it is marked isDisabled: true in metadata — after filing, the choice is locked.

Exported types

The non-primitive types in the ready state are all re-exported from @gusto/embedded-react-sdk:

TypeWhat it is
UseEmployeeStateTaxesFormReadyThe full ready-state object (the isLoading: false branch). Use it as the prop type for components that receive a ready form, so you don't repeat the Extract<…> narrowing.
EmployeeStateTaxesListAPI record for one state's tax answers. Each entry in data.employeeStateTaxes is one of these.
StateTaxFieldsGroupOne state's render-ready bundle: { state, questions }.
StateTaxQuestionFieldEntryThe discriminated entry for a single question — type plus metadata plus a bound Field component.
EmployeeStateTaxesFieldsMetadataStatic field metadata keyed by full form path (states.<STATE>.<camelKey>), with isRequired / isDisabled and option lists.
EmployeeStateTaxesFormOutputsThe submit-time form data shape: { states: Record<string, Record<string, StateTaxValue>> }. Returned by getFormSubmissionValues().
HookSubmitResult<T>Standard SDK submit-result envelope: { mode: 'update', data: T }.
HookErrorHandlingStandard SDK error-handling object exposed by composeErrorHandler.
import type {
UseEmployeeStateTaxesFormReady,
StateTaxFieldsGroup,
StateTaxQuestionFieldEntry,
EmployeeStateTaxesFieldsMetadata,
EmployeeStateTaxesFormOutputs,
} from '@gusto/embedded-react-sdk'

Choosing a field component

Each variant's FieldComponent must match the prop contract of the SDK UI primitive that variant renders. Discriminate on question.type first, then supply a component whose props satisfy the matching SDK prop type:

VariantRequired FieldComponent shapeSDK primitive it replaces
selectComponentType<SelectProps>Components.Select
radioComponentType<RadioGroupProps>Components.RadioGroup
textComponentType<TextInputProps>Components.TextInput
numberComponentType<NumberInputProps>Components.NumberInput
currencyComponentType<NumberInputProps>Components.NumberInput
dateComponentType<DatePickerProps>Components.DatePicker

SelectProps, RadioGroupProps, TextInputProps, NumberInputProps, and DatePickerProps are all re-exported from @gusto/embedded-react-sdk, alongside the variant-specific *StateTaxFieldProps types whose FieldComponent field encodes the same constraint (e.g. SelectStateTaxFieldProps['FieldComponent'] is ComponentType<SelectProps>).

A minimal type-safe override:

import { MyDesignSystemSelect } from './forms/MyDesignSystemSelect'

if (question.type === 'select') {
return <question.Field FieldComponent={MyDesignSystemSelect} />
}

Per-question overrides

Each Field accepts:

  • label and description to override the API-supplied defaults (the API description is otherwise rendered verbatim, sanitized via DOMPurify).
  • FieldComponent to swap the underlying control with one of your own. The prop type is variant-specificSelectStateTaxFieldProps['FieldComponent'] is ComponentType<SelectProps>, DateStateTaxFieldProps['FieldComponent'] is ComponentType<DatePickerProps>, and so on — so discriminate on question.type first.

You can branch on question.type, group.state, and question.questionId. They sit on a sliding scale from compile-time-safe to fragile:

Branch onStabilityUse it for
question.typeClosed union of 6 strings owned by this hook. Adding a variant is an SDK breaking change.Design-system primitive swaps. Safe and exhaustively typed.
group.stateAPI-driven 2-letter code. Stable for known states, but a state's question set can change as tax law changes.Geographic copy or branding. Re-test when a new state opens up.
question.questionIdCamel-cased API key. The set of keys for a state is API-driven and evolves as questions are added or renamed.One-off overrides for a specific question. Treat as a soft coupling.

Caution. Branching on questionId or state is a soft coupling to the API contract. When a new state question is introduced, renamed, or split in two, hardcoded if (question.questionId === '…') branches silently fall through to the default render. Audit these branches as part of any state-tax-related upgrade, and prefer type-level overrides whenever the same change applies to a whole class of fields.

Combining type, state, and questionId

import type { StateTaxFieldsGroup, StateTaxQuestionFieldEntry } from '@gusto/embedded-react-sdk'

import { MyDesignSystemSelect } from './forms/MyDesignSystemSelect'
import { MyDesignSystemDatePicker } from './forms/MyDesignSystemDatePicker'
import { CountyAutocomplete } from './forms/CountyAutocomplete'

// Explicit allow-list of Indiana questionIds we treat as county selects.
// Audit this set whenever the IN state-tax form changes upstream.
const IN_COUNTY_QUESTION_IDS = new Set([
'currentEmploymentCounty',
'currentResidenceCounty',
'previousEmploymentCounty',
'previousResidenceCounty',
])

function RenderQuestion({
group,
question,
}: {
group: StateTaxFieldsGroup
question: StateTaxQuestionFieldEntry
}) {
// 1. questionId-level one-off — relabel a single question.
if (question.questionId === 'fileNewHireReport') {
return (
<question.Field
label="Will we file the state new-hire report on your behalf?"
validationMessages={{ REQUIRED: 'Please choose an option' }}
/>
)
}

// 2. State + questionId one-off — Indiana ships four county Selects we want
// rendered with a tailored autocomplete. Matching an explicit allow-list
// (not a substring) means newly-added IN questions don't silently inherit it.
if (
group.state === 'IN' &&
question.type === 'select' &&
IN_COUNTY_QUESTION_IDS.has(question.questionId)
) {
return (
<question.Field
description="Type to filter Indiana counties."
FieldComponent={CountyAutocomplete}
/>
)
}

// 3. State-level description override — soften NJ's verbose API copy.
if (group.state === 'NJ' && question.questionId === 'filingStatus') {
return (
<question.Field description="Pick the filing status that matches your most recent NJ-W4." />
)
}

// 4. Type-level swap — replace every Select / Date with a design-system
// primitive. Safe to add and forget; new questions of these variants
// pick it up automatically.
switch (question.type) {
case 'select':
return <question.Field FieldComponent={MyDesignSystemSelect} />
case 'date':
return <question.Field FieldComponent={MyDesignSystemDatePicker} />
case 'radio':
case 'text':
case 'number':
case 'currency':
return <question.Field />
}
}

A few things worth noting:

  • The questionId/state branches sit above the type switch so a one-off override wins over the broad design-system swap.
  • Keep key={question.questionId} on the rendered field — React relies on it to maintain field identity across re-renders.
  • currency and number share the same NumberInputProps shape, so a single design-system Number override can collapse them: case 'number': case 'currency': return <question.Field FieldComponent={MyNumberInput} />.
  • question.questionId is the camelCase form of the API key (filingStatus, not filing_status); keep string comparisons in camelCase to stay aligned with the hook's contract.

Rendering without a provider

The example above wires the form through SDKFormProvider. To render without it, pass the hook result to each field via formHookResult:

groups.map(group =>
group.questions.map(question => (
<question.Field key={`${group.state}-${question.questionId}`} formHookResult={stateTaxes} />
)),
)

Endpoints