From e872b0b239a4e43d324764c2d817d293d1bb1ffb Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 25 Sep 2026 08:37:23 +0200 Subject: [PATCH 1/6] feat(onboarding): add job_title_eligibility feature flag Splits the flag declaration out of #1395 so it can land on its own: adds 'job_title_eligibility' to the OnboardingFeatures union (with its JSDoc) and enables it in the example app. No behavior wired up yet. Co-Authored-By: Claude Sonnet 5 --- example/src/flows/Onboarding/constants.ts | 1 + src/flows/Onboarding/types.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/example/src/flows/Onboarding/constants.ts b/example/src/flows/Onboarding/constants.ts index 1e3b66de6..5f9964ce4 100644 --- a/example/src/flows/Onboarding/constants.ts +++ b/example/src/flows/Onboarding/constants.ts @@ -12,6 +12,7 @@ export const ONBOARDING_OPTIONS = { 'ea_preview', 'pre_onboarding_requirements', 'daily_schedule', + 'job_title_eligibility', ] as const, jsonSchemaVersion: ONBOARDING_JSON_SCHEMA_VERSION, jsonSchemaVersionByCountry: ONBOARDING_JSON_SCHEMA_VERSION_BY_COUNTRY, diff --git a/src/flows/Onboarding/types.ts b/src/flows/Onboarding/types.ts index b2b355ce7..5bf86dfb5 100644 --- a/src/flows/Onboarding/types.ts +++ b/src/flows/Onboarding/types.ts @@ -65,7 +65,8 @@ type OnboardingFeatures = | 'ea_preview' | 'pre_onboarding_requirements' | 'split_salary_description' - | 'daily_schedule'; + | 'daily_schedule' + | 'job_title_eligibility'; /** * JSON schema version configuration for a specific country @@ -180,6 +181,10 @@ export type OnboardingFlowProps = { * currency conversion toggle to the text component as three separate slots (`description`, * `meta.helpCenter`, `descriptionSuffix`) instead of packing them into `description`. * Custom text components must render `fieldData.descriptionSuffix` to keep the conversion toggle. + * - 'job_title_eligibility': Run the job title eligibility check on the contract details step once + * the role fields are filled. Its outcome fills `additional_job_title_eligibility_check_slug` and, + * when the schema declares it, `additional_job_title_eligibility_check_result`, so fields that + * depend on the verdict (such as `employer_acknowledges_risk`) react to it. */ features?: OnboardingFeatures[]; }; From 33aabca70500fe24f03f3a3ed57283f6b4c3004f Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 25 Sep 2026 10:15:50 +0200 Subject: [PATCH 2/6] feat(onboarding): wire onBlur events to job title eligibility check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the useJobTitleEligibilityCheck hook and jobTitleEligibilityCheckOptions query, and wires OnboardingForm's onBlur to call the flowBag's new checkJobTitleEligibility(values). The check only runs on the contract_details step, when 'job_title_eligibility' is enabled, and when the role fields (role_description, role_is_onsite, role_requires_license) are filled and valid. Dedup: params are compared against the previous check (fast-deep-equal) before firing a request, and jobTitleEligibilityCheckOptions sets staleTime: Infinity since the query key already encodes employmentId + params. Without both, react-query's built-in dedup only covers truly concurrent calls with an identical key — an imperative queryClient.query() call is not a mounted observer, so it treats every call as a fresh mount and refetches under the default staleTime: 0. The result isn't consumed yet (no UI reacts to eligibility yet) - follow-up. Co-Authored-By: Claude Sonnet 5 --- src/flows/Onboarding/api.ts | 58 +++++++- .../Onboarding/components/OnboardingForm.tsx | 1 + src/flows/Onboarding/hooks.tsx | 31 +++++ .../hooks/useJobTitleEligibilityCheck.tsx | 129 ++++++++++++++++++ src/flows/Onboarding/utils.ts | 54 ++++++++ 5 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx diff --git a/src/flows/Onboarding/api.ts b/src/flows/Onboarding/api.ts index 99ea7fc77..e17ccbb72 100644 --- a/src/flows/Onboarding/api.ts +++ b/src/flows/Onboarding/api.ts @@ -1,4 +1,9 @@ -import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query'; +import { + queryOptions, + useMutation, + useQuery, + UseQueryResult, +} from '@tanstack/react-query'; import { FieldValues } from 'react-hook-form'; import { Client } from '@/src/client/client'; import { @@ -32,6 +37,8 @@ import { getV1OnboardingEmploymentsEmploymentIdPreOnboardingRequirements, getV1OnboardingEmploymentsEmploymentIdPreOnboardingDocumentsId, PostV1OnboardingEmploymentsEmploymentIdPreOnboardingRequirementsRequirementSlugDocumentsData, + CreateJobTitleEligibilityCheckParams, + postV2EmploymentsEmploymentIdJobTitleEligibilityCheck, } from '@/src/client'; import { useClient } from '@/src/context'; @@ -56,6 +63,11 @@ import { import { createHeadlessForm } from '@/src/common/createHeadlessForm'; import { countriesOptions } from '@/src/common/api/countries'; import { useMemo } from 'react'; +import { $TSFixMe } from '@/scripts/types'; +import { + extractFieldErrors, + MutationErrorStructure, +} from '@/src/lib/mutations'; export const useCompany = (companyId: string) => { const { client } = useClient(); @@ -861,3 +873,47 @@ export const useEmploymentAgreementPreview = ( select: (response) => response.data?.data.employment_agreement, }); }; + +export const jobTitleEligibilityCheckOptions = ( + client: Client, + employmentId: string, + params: CreateJobTitleEligibilityCheckParams, +) => + queryOptions({ + queryKey: ['job-title-eligibility-check', employmentId, params] as const, + retry: false, + staleTime: Infinity, + queryFn: async ({ signal }) => { + const response = + await postV2EmploymentsEmploymentIdJobTitleEligibilityCheck({ + client, + headers: { + Authorization: ``, + }, + body: params, + path: { + employment_id: employmentId, + }, + signal, + }); + + if (response.error || !response.data) { + const errorData = + (response.error as $TSFixMe)?.error || response.error || {}; + const error: MutationErrorStructure = { + error: new Error( + typeof errorData.message === 'string' + ? errorData.message + : 'Something went wrong. Please try again later.', + ), + rawError: response.error as Record, + normalizedErrors: errorData.errors || {}, + fieldErrors: extractFieldErrors(response.error), + response: response.response, + }; + throw error; + } + + return response.data.data.job_title_eligibility_check; + }, + }); diff --git a/src/flows/Onboarding/components/OnboardingForm.tsx b/src/flows/Onboarding/components/OnboardingForm.tsx index 6cb242cf4..c3bd5f222 100644 --- a/src/flows/Onboarding/components/OnboardingForm.tsx +++ b/src/flows/Onboarding/components/OnboardingForm.tsx @@ -102,6 +102,7 @@ export function OnboardingForm({ id={formId} onSubmit={form.handleSubmit(handleSubmit)} className='space-y-4 RemoteFlows__OnboardingForm' + onBlur={() => onboardingBag.checkJobTitleEligibility(form.getValues())} > ; @@ -571,6 +572,10 @@ export const useOnboarding = ({ Boolean(employmentId)), ); + const isJobTitleEligibilityEnabled = Boolean( + options?.features?.includes('job_title_eligibility'), + ); + const { data: basicInformationForm, isLoading: isLoadingBasicInformationForm, @@ -1355,6 +1360,24 @@ export const useOnboarding = ({ ], ); + const jobTitleEligibility = useJobTitleEligibilityCheck({ + enabled: isJobTitleEligibilityEnabled, + employmentId: internalEmploymentId, + currentStepName, + contractDetailsFields: stepFields.contract_details, + stepValues: stepState.values?.contract_details, + initialContractDetailsValues: initialValues.contract_details, + fieldValues, + submittedJobTitle: employment?.basic_information?.job_title as + | string + | undefined, + fallbackJobTitle: basicInformationInitialValues.job_title as + | string + | undefined, + parseFormValues, + handleValidation, + }); + return { /** * Employment id passed useful to be used between components @@ -1435,6 +1458,14 @@ export const useOnboarding = ({ */ parseFormValues, + /** + * Runs the job title eligibility check with the current contract details values when the + * 'job_title_eligibility' feature is enabled and the role fields are filled. The prebuilt form calls it + * on blur; the check also runs when entering the contract details step and before submitting it. + * @param values - Current form values + */ + checkJobTitleEligibility: jobTitleEligibility.check, + /** * Function to handle form submission * @param values - Form values to submit diff --git a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx new file mode 100644 index 000000000..218756adb --- /dev/null +++ b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx @@ -0,0 +1,129 @@ +import { ValidationResult } from '@remoteoss/remote-json-schema-form-kit'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import equal from 'fast-deep-equal'; +import { useState } from 'react'; +import { FieldValues } from 'react-hook-form'; +import { CreateJobTitleEligibilityCheckParams } from '@/src/client'; +import { Client } from '@/src/client/client'; +import { useClient } from '@/src/context'; +import { jobTitleEligibilityCheckOptions } from '@/src/flows/Onboarding/api'; +import { + getJobTitleEligibilityParams, + StepKeys, +} from '@/src/flows/Onboarding/utils'; +import { JSFFields } from '@/src/types/remoteFlows'; + +/** + * Owns the state and query for the job title eligibility check. + */ +export const useJobTitleEligibilityState = ({ + employmentId, + enabled, + currentStepName, +}: { + employmentId: string | undefined; + enabled: boolean; + currentStepName: StepKeys; +}) => { + const { client } = useClient(); + const [params, setParams] = + useState(null); + + const getOptions = (checkParams: CreateJobTitleEligibilityCheckParams) => + jobTitleEligibilityCheckOptions( + client as Client, + employmentId as string, + checkParams, + ); + + const query = useQuery({ + ...getOptions(params ?? {}), + enabled: Boolean( + enabled && + employmentId && + currentStepName === 'contract_details' && + params, + ), + }); + + return { + params, + setParams, + getOptions, + query, + }; +}; + +export const useJobTitleEligibilityCheck = ({ + enabled, + employmentId, + currentStepName, + contractDetailsFields, + fallbackJobTitle, + submittedJobTitle, + parseFormValues, + handleValidation, +}: { + enabled: boolean; + employmentId: string | undefined; + currentStepName: StepKeys; + contractDetailsFields: JSFFields; + stepValues: Record | undefined; + initialContractDetailsValues: Record; + fieldValues: FieldValues; + fallbackJobTitle: string | undefined; + parseFormValues: (values: FieldValues) => Promise>; + handleValidation: ( + values: FieldValues, + ) => Promise; + submittedJobTitle: string | undefined; +}) => { + console.log('useJobTitleEligibilityCheck hook'); + const queryClient = useQueryClient(); + const { setParams, getOptions, query } = useJobTitleEligibilityState({ + employmentId, + enabled, + currentStepName, + }); + + const jobTitle = submittedJobTitle ?? fallbackJobTitle; + + const check = async (values: FieldValues) => { + if (!enabled || !employmentId || currentStepName !== 'contract_details') { + console.log('Job title eligibility check not running yet'); + return; + } + // check validation to see if form is valid + const validation = await handleValidation(values); + console.log('Validation result', validation); + // parse form values to get the values for the job title eligibility check + const parsedValues = await parseFormValues(values); + console.log('Parsed values', parsedValues); + // get the parameters for the job title eligibility check + const nextParams = getJobTitleEligibilityParams( + contractDetailsFields, + parsedValues, + validation?.formErrors, + jobTitle, + ); + console.log('Next params', nextParams); + let paramsChanged = false; + setParams((current) => { + paramsChanged = !equal(current, nextParams); + return paramsChanged ? nextParams : current; + }); + if (nextParams && paramsChanged) { + console.log('Fetching job title eligibility check', nextParams); + await queryClient + .query(getOptions(nextParams)) + .catch(() => + console.error('Failed to fetch job title eligibility check'), + ); + } + }; + + return { + isFetching: query.isFetching, + check, + }; +}; diff --git a/src/flows/Onboarding/utils.ts b/src/flows/Onboarding/utils.ts index ccd1aac6a..9f1a98c6b 100644 --- a/src/flows/Onboarding/utils.ts +++ b/src/flows/Onboarding/utils.ts @@ -1,5 +1,7 @@ +import { CreateJobTitleEligibilityCheckParams } from '@/src/client'; import { Employment, OnboardingFlowProps } from '@/src/flows/Onboarding/types'; import { Step } from '@/src/flows/useStepState'; +import { JSFField, JSFFields } from '@/src/types/remoteFlows'; export type StepKeys = | 'select_country' @@ -221,3 +223,55 @@ export const getEngagementAgreementDetailsSchemaVersion = ( options?.jsonSchemaVersion?.engagement_agreement_details || DEFAULT_VERSION ); }; + +export const JOB_TITLE_ELIGIBILITY_SLUG_FIELD = + 'additional_job_title_eligibility_check_slug'; + +export const JOB_TITLE_ELIGIBILITY_RESULT_FIELD = + 'additional_job_title_eligibility_check_result'; + +const JOB_TITLE_ELIGIBILITY_PARAM_FIELDS = [ + 'role_description', + 'role_is_onsite', + 'role_requires_license', +]; + +const isFilled = (value: unknown) => + value !== undefined && value !== null && value !== ''; + +export const getJobTitleEligibilityParams = ( + fields: JSFFields, + values: Record, + formErrors?: Record | null, + jobTitle?: string, +): CreateJobTitleEligibilityCheckParams | null => { + const jsfFields = fields as JSFField[]; + if ( + !jsfFields.some((field) => field.name === JOB_TITLE_ELIGIBILITY_SLUG_FIELD) + ) { + return null; + } + + const paramFields = jsfFields.filter( + (field) => + JOB_TITLE_ELIGIBILITY_PARAM_FIELDS.includes(field.name) && + field.isVisible !== false, + ); + + const isComplete = + paramFields.length > 0 && + paramFields.every( + (field) => isFilled(values[field.name]) && !formErrors?.[field.name], + ); + + if (!isComplete) { + return null; + } + + return { + ...(isFilled(jobTitle) ? { job_title: jobTitle } : {}), + ...Object.fromEntries( + paramFields.map((field) => [field.name, values[field.name]]), + ), + } as CreateJobTitleEligibilityCheckParams; +}; From 330242b1a485f531fe5532e0069e169b47ec994e Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 25 Sep 2026 10:43:10 +0200 Subject: [PATCH 3/6] fix(onboarding): drop debug logs and fix job title eligibility dedup Removes the console.log statements left over from development. Also fixes a real bug surfaced while writing tests for this: paramsChanged was read immediately after calling setParams(updaterFn), assuming the functional updater runs synchronously. It doesn't reliably do that, so paramsChanged came back false almost every time - meaning the eligibility check never actually fired a request, for any input. Fixed by comparing against the params value already in the closure before calling setParams, instead of depending on a side effect inside the updater. Co-Authored-By: Claude Sonnet 5 --- .claude/settings.json | 13 ++++++++++++ .gitignore | 3 ++- .../hooks/useJobTitleEligibilityCheck.tsx | 20 +++++-------------- 3 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..e4716e446 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run type-check)", + "Bash(git fetch *)", + "Bash(npm run lint)", + "mcp__chrome-devtools__take_snapshot", + "mcp__chrome-devtools__get_console_message", + "Bash(mkdir -p /tmp/*)", + "Bash(cat > /tmp/*)" + ] + } +} diff --git a/.gitignore b/.gitignore index 920e9ce1d..3df12572b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ npm-debug.log* node_modules dist *.local +CLAUDE.local.md # Editor directories and files .vscode/* @@ -16,4 +17,4 @@ dist coverage example/public/ out -example/tsconfig.e2e.tsbuildinfo \ No newline at end of file +example/tsconfig.e2e.tsbuildinfo diff --git a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx index 218756adb..e93c9af60 100644 --- a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx +++ b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx @@ -78,9 +78,8 @@ export const useJobTitleEligibilityCheck = ({ ) => Promise; submittedJobTitle: string | undefined; }) => { - console.log('useJobTitleEligibilityCheck hook'); const queryClient = useQueryClient(); - const { setParams, getOptions, query } = useJobTitleEligibilityState({ + const { params, setParams, getOptions, query } = useJobTitleEligibilityState({ employmentId, enabled, currentStepName, @@ -90,30 +89,21 @@ export const useJobTitleEligibilityCheck = ({ const check = async (values: FieldValues) => { if (!enabled || !employmentId || currentStepName !== 'contract_details') { - console.log('Job title eligibility check not running yet'); return; } - // check validation to see if form is valid const validation = await handleValidation(values); - console.log('Validation result', validation); - // parse form values to get the values for the job title eligibility check const parsedValues = await parseFormValues(values); - console.log('Parsed values', parsedValues); - // get the parameters for the job title eligibility check const nextParams = getJobTitleEligibilityParams( contractDetailsFields, parsedValues, validation?.formErrors, jobTitle, ); - console.log('Next params', nextParams); - let paramsChanged = false; - setParams((current) => { - paramsChanged = !equal(current, nextParams); - return paramsChanged ? nextParams : current; - }); + const paramsChanged = !equal(params, nextParams); + if (paramsChanged) { + setParams(nextParams); + } if (nextParams && paramsChanged) { - console.log('Fetching job title eligibility check', nextParams); await queryClient .query(getOptions(nextParams)) .catch(() => From edadc3cfcd4fdd06842441fc26007cf6c949cc6e Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 25 Sep 2026 10:44:17 +0200 Subject: [PATCH 4/6] chore: remove accidentally committed local settings file .claude/settings.json is a personal/local permission-allowlist file, not meant for this PR - it got swept into the previous commit because it was already staged from earlier local work. Co-Authored-By: Claude Sonnet 5 --- .claude/settings.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index e4716e446..000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(npm run type-check)", - "Bash(git fetch *)", - "Bash(npm run lint)", - "mcp__chrome-devtools__take_snapshot", - "mcp__chrome-devtools__get_console_message", - "Bash(mkdir -p /tmp/*)", - "Bash(cat > /tmp/*)" - ] - } -} From 382c58f3cc8186684b597b13eeac576e0a59bf92 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 25 Sep 2026 10:57:51 +0200 Subject: [PATCH 5/6] fix(onboarding): match checkJobTitleEligibility's docs to its behavior The JSDoc claimed the check also runs on entering the contract details step and before submitting it, but nothing calls it that way - only OnboardingForm's onBlur does. Since useOnboarding is reachable through the public ./flows/* entry point, this overstated the contract for anyone building a custom UI around the headless hook. Reworded to describe only what's actually wired up, and pointed custom UIs at calling checkJobTitleEligibility themselves for other trigger points instead of implying it happens automatically. Also drops stepValues, initialContractDetailsValues, and fieldValues from useJobTitleEligibilityCheck's params - they were threaded through from hooks.tsx but never used inside the hook, leftover scaffolding for the same unimplemented triggers. Co-Authored-By: Claude Sonnet 5 --- src/flows/Onboarding/hooks.tsx | 10 ++++------ .../Onboarding/hooks/useJobTitleEligibilityCheck.tsx | 3 --- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/flows/Onboarding/hooks.tsx b/src/flows/Onboarding/hooks.tsx index 2e2811d98..bb3ac3b2d 100644 --- a/src/flows/Onboarding/hooks.tsx +++ b/src/flows/Onboarding/hooks.tsx @@ -1365,9 +1365,6 @@ export const useOnboarding = ({ employmentId: internalEmploymentId, currentStepName, contractDetailsFields: stepFields.contract_details, - stepValues: stepState.values?.contract_details, - initialContractDetailsValues: initialValues.contract_details, - fieldValues, submittedJobTitle: employment?.basic_information?.job_title as | string | undefined, @@ -1459,9 +1456,10 @@ export const useOnboarding = ({ parseFormValues, /** - * Runs the job title eligibility check with the current contract details values when the - * 'job_title_eligibility' feature is enabled and the role fields are filled. The prebuilt form calls it - * on blur; the check also runs when entering the contract details step and before submitting it. + * Runs the job title eligibility check with the given contract details values when the + * 'job_title_eligibility' feature is enabled, the current step is contract_details, and the + * role fields are filled. The prebuilt form calls it on blur; call it yourself from a custom + * UI to trigger the same check at another point (e.g. on step entry or before submitting). * @param values - Current form values */ checkJobTitleEligibility: jobTitleEligibility.check, diff --git a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx index e93c9af60..659012657 100644 --- a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx +++ b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx @@ -68,9 +68,6 @@ export const useJobTitleEligibilityCheck = ({ employmentId: string | undefined; currentStepName: StepKeys; contractDetailsFields: JSFFields; - stepValues: Record | undefined; - initialContractDetailsValues: Record; - fieldValues: FieldValues; fallbackJobTitle: string | undefined; parseFormValues: (values: FieldValues) => Promise>; handleValidation: ( From 7686a5ea41f151b17282a0ec9331b9247a159d0a Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 25 Sep 2026 11:04:07 +0200 Subject: [PATCH 6/6] fix(onboarding): close race between overlapping eligibility checks check() compared nextParams against params captured from the render closure after two awaits (handleValidation, parseFormValues). If a second blur's check() started before the first one's setParams had committed a re-render, both calls would compare against the same stale params - depending on timing, that can either fire a duplicate request or wrongly treat a later complete result as unchanged and skip it. Flagged by Bugbot on PR #1406, independently of the same race I'd already called out in review. Fixed by tracking params in a ref alongside the state (state still drives the reactive useQuery's enabled/key). Ref writes are synchronous and immediately visible to any concurrently-resolving check() call, so the comparison always sees the latest known params regardless of render timing, closing the window entirely rather than narrowing it. Co-Authored-By: Claude Sonnet 5 --- .../hooks/useJobTitleEligibilityCheck.tsx | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx index 659012657..e2c96b63f 100644 --- a/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx +++ b/src/flows/Onboarding/hooks/useJobTitleEligibilityCheck.tsx @@ -1,7 +1,7 @@ import { ValidationResult } from '@remoteoss/remote-json-schema-form-kit'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import equal from 'fast-deep-equal'; -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { FieldValues } from 'react-hook-form'; import { CreateJobTitleEligibilityCheckParams } from '@/src/client'; import { Client } from '@/src/client/client'; @@ -26,8 +26,14 @@ export const useJobTitleEligibilityState = ({ currentStepName: StepKeys; }) => { const { client } = useClient(); - const [params, setParams] = + const [params, setParamsState] = useState(null); + const paramsRef = useRef(params); + + const setParams = (next: CreateJobTitleEligibilityCheckParams | null) => { + paramsRef.current = next; + setParamsState(next); + }; const getOptions = (checkParams: CreateJobTitleEligibilityCheckParams) => jobTitleEligibilityCheckOptions( @@ -47,7 +53,7 @@ export const useJobTitleEligibilityState = ({ }); return { - params, + paramsRef, setParams, getOptions, query, @@ -76,11 +82,12 @@ export const useJobTitleEligibilityCheck = ({ submittedJobTitle: string | undefined; }) => { const queryClient = useQueryClient(); - const { params, setParams, getOptions, query } = useJobTitleEligibilityState({ - employmentId, - enabled, - currentStepName, - }); + const { paramsRef, setParams, getOptions, query } = + useJobTitleEligibilityState({ + employmentId, + enabled, + currentStepName, + }); const jobTitle = submittedJobTitle ?? fallbackJobTitle; @@ -96,7 +103,7 @@ export const useJobTitleEligibilityCheck = ({ validation?.formErrors, jobTitle, ); - const paramsChanged = !equal(params, nextParams); + const paramsChanged = !equal(paramsRef.current, nextParams); if (paramsChanged) { setParams(nextParams); }