Signature Search & Validation
3.1 SignatureResultContextProvider
Location: src/context/signatureResultsContext/signatureResultContext.tsx
Purpose: Manages the complete signature entry workflow including form data, search results, and validation state.
Props:
{
children: React.ReactNode;
jurisdictionType: string | null;
jurisdictionName: string | null;
}
State Management:
-
Form Data Array:
- Array of
ESFormEntryobjects - One entry per required signature
- Initialized based on
requiredSignatures
- Array of
-
Search Results:
- Array of
VoterSearchResultTypeExtended - Mapped to form entries by
searchIndex - Includes validation status and details
- Array of
-
Active Step:
"form"- Entry form"validate"- Validation/results view
-
Page Management:
- Tracks current page number
- Calculates total pages from format details
- Resets form data on page change
Key Logic:
-
Debounced Search:
- Uses 500ms debounce on form data
- Triggers search when entry is complete
- Prevents excessive API calls
-
Result Updates:
- Compares new search params with existing
- Only searches if values changed
- Updates or adds results based on
searchIndex
-
Petition Pre-creation:
- Creates petition on last page
- Pre-fetches next route
- Optimizes navigation performance
Dependencies:
useESValidation- Search hookusePetitionContext- Petition stateuseSignatureStep- Step navigationuseDebounce- Debounce utility
3.2 updateSearchResults
Location: src/context/signatureResultsContext/processFunc.ts
Purpose: Merges new search results with existing results, handling jurisdiction validation and multiple matches.
Signature:
function updateSearchResults(
newResult: ESSearchResult,
entry: ESFormEntry,
searchResults: VoterSearchResultTypeExtended[],
jurisdictionType: string,
jurisdictionName: string
): VoterSearchResultTypeExtended[]
Parameters:
newResult- Raw search results from APIentry- Original form entry that triggered searchsearchResults- Current search results arrayjurisdictionType- "city" or "county"jurisdictionName- Jurisdiction name to validate against
Returns: Updated search results array
Logic Flow:
-
Jurisdiction Check:
- Calls
checkJurisdictionto validate all results - Marks results as invalid if wrong jurisdiction
- Calls
-
Find Existing Entry:
- Locates result with matching
searchIndex
- Locates result with matching
-
Update First Result:
- Replaces existing entry with first result
- Preserves
originalParamsandsearchIndex - Sets
_typeto "results"
-
Handle Multiple Results:
- If more than one result, adds additional entries
- Maintains same
searchIndexfor all matches - Generates unique
search_idfor each
-
Return Updated Array:
- Returns new array with all updates
- Preserves order by
searchIndex
Example:
// Entry with searchIndex: 1
// API returns 2 matches
// Result: 2 entries in array, both with searchIndex: 1
// search_id: "1" and "11"
Notes:
- Always returns array, even if no results
- Preserves existing results not being updated
- Handles empty result sets gracefully
3.3 checkJurisdiction
Location: src/context/signatureResultsContext/processFunc.ts
Purpose: Validates search results against campaign jurisdiction requirements.
Signature:
function checkJurisdiction({
newResult,
jurisdictionType,
jurisdictionName
}: {
newResult: ESSearchResult;
jurisdictionType: string;
jurisdictionName: string;
}): ESSearchResult
Parameters:
newResult- Search results from APIjurisdictionType- "city" or "county"jurisdictionName- Required jurisdiction name
Returns: Results with jurisdiction validation applied
Logic:
-
Skip Invalid Results:
- Results already marked "invalid" are unchanged
- Results marked as "not found" are unchanged
-
City Jurisdiction:
- Compares
address_city_jurisdictionwithjurisdictionName - Marks as invalid if mismatch
- Compares
-
County Jurisdiction:
- Compares
address_countywithjurisdictionName - Marks as invalid if mismatch
- Compares
-
Status Update:
- Sets
status: "invalid"if wrong jurisdiction - Sets
statusDetails: "Wrong Jurisdiction" - Resets
selectedtonull
- Sets
Data Paths:
- City:
result._source.v_hh_address.address_city_jurisdiction - County:
result._source.v_hh_address.address_county
Notes:
- Case-insensitive comparison (via
toLocaleLowerCase()) - Preserves other status information
- Returns all results, not just valid ones
3.4 isWrongJurisdiction
Location: src/context/signatureResultsContext/processFunc.ts
Purpose: Helper function to check if a single result is in the wrong jurisdiction.
Signature:
function isWrongJurisdiction({
result,
jurisdictionType,
jurisdictionName
}: {
result: VoterSearchResultTypeExtended;
jurisdictionType: string;
jurisdictionName: string;
}): boolean
Returns: true if wrong jurisdiction, false otherwise
Logic:
- City: Checks
address_city_jurisdiction - County: Checks
address_county - Returns
falsefor unknown jurisdiction types
3.5 processFilledEntries
Location: src/context/signatureResultsContext/processFilledEntries.ts
Purpose: Processes form entries to identify completed entries and handle invalid entries.
Signature:
function processFilledEntries({
formDataArray,
searchResults,
setSearchResults
}: ProcessEntriesParams): ProcessEntriesReturn
Parameters:
formDataArray- Array of form entriessearchResults- Current search resultssetSearchResults- State setter function
Returns:
{
filledEntries: ESFormEntry[];
updatedSearchResults: VoterSearchResultTypeExtended[];
}
Logic Flow:
-
Handle Invalid Entries (
noSearchcategory):- Creates invalid result object
- Adds to search results if not exists
- Updates existing result if found
- Returns
falseto exclude from filled entries
-
Handle Valid but Incomplete Entries:
- If
status === "valid"but entry incomplete - Removes associated search results
- Returns
falseto exclude
- If
-
Filter Complete Entries:
- Uses
isEntryComplete()to validate - Only includes entries with
searchIndex !== 0 - Returns array of entries ready for search
- Uses
Invalid Entry Structure:
{
_index: "voter",
_id: searchIndex.toString(),
_type: "noSearch",
searchIndex: entry.searchIndex,
status: "invalid",
statusDetails: entry.statusDetails,
selected: false,
search_id: `${searchIndex}`,
originalParams: entry,
newRegistrationData?: {...} // If regNo exists
}
Notes:
- Mutates
searchResultsviasetSearchResults - Returns copy of updated results
- Handles edge cases for incomplete valid entries
3.6 searchMultipleAndReturn
Location: src/hooks/add-signatures/useESValidation.tsx
Purpose: Performs multiple voter searches sequentially and aggregates results with duplicate checking.
Signature:
async function searchMultipleAndReturn(
paramsArray: SearchParams[],
campaignId: string
): Promise<ESSearchResult>
Parameters:
paramsArray- Array of search parameters (one per form entry)campaignId- Campaign ID for duplicate checking
Returns: Combined search results with validation status
Logic Flow:
-
Initialize Combined Results:
{
persons: [],
hitsCount: 0,
totalHits: 0
} -
Loop Through Parameters:
- Makes API call for each parameter
- Handles errors per search
-
Handle No Results:
- Creates placeholder "not found" result
- Sets
status: "bad",statusDetails: "no match" - Sets
isNotFoundResult: true - Continues to next search
-
Process Valid Results:
- For each person in results:
- Checks duplicate via
checkVoterDuplicate() - Sets status:
"invalid"if duplicate,"valid"otherwise - Sets statusDetails:
"duplicate"or"matched" - Adds
searchIndexandoriginalParams
- Checks duplicate via
- For each person in results:
-
Aggregate Results:
- Merges persons arrays
- Sums
hitsCountandtotalHits - Preserves error information if present
-
Error Handling:
- Throws error if API call fails
- Includes error message from API response
Duplicate Checking:
- Calls
/api/duplicateendpoint - Checks if
voter_idexists in campaign signatures - Returns
trueif duplicate found
Result Structure:
{
persons: [
{
...voterData,
status: "valid" | "invalid",
statusDetails: "matched" | "duplicate",
searchIndex: number,
originalParams: SearchParams,
isNotFoundResult: boolean
}
],
hitsCount: number,
totalHits: number,
error?: string,
errorCode?: number
}
Performance:
- Sequential execution (not parallel)
- Each search waits for previous to complete
- Duplicate checks run in parallel via
Promise.all()
3.7 checkVoterDuplicate
Location: src/hooks/add-signatures/useESValidation.tsx
Purpose: Checks if a voter has already been added to a campaign.
Signature:
async function checkVoterDuplicate(
voterId: string,
campaignId: string
): Promise<boolean>
Parameters:
voterId- Voter ID from search resultcampaignId- Campaign ID to check
Returns: true if duplicate, false otherwise
Implementation:
- POSTs to
/api/duplicateendpoint - Sends
{ campaignId, voterId } - Returns
data.existsfrom response
Error Handling:
- Returns
falseon error (assumes not duplicate) - Logs error to console
3.8 isEntryComplete
Location: src/lib/utils.ts
Purpose: Validates that a form entry has all required fields filled.
Signature:
function isEntryComplete(entry: ESFormEntry): boolean
Validation Rules:
firstName- Must be non-empty after trimlastName- Must be non-empty after trimhouseNumber- Must be non-empty after trimzip- Must be non-empty after trim AND exactly 5 characters
Returns: true if all validations pass, false otherwise
Example:
isEntryComplete({
firstName: "John",
lastName: "Doe",
houseNumber: "123",
zip: "12345"
}); // Returns: true
isEntryComplete({
firstName: "John",
lastName: "",
houseNumber: "123",
zip: "1234" // Only 4 digits
}); // Returns: false
3.9 isEntryEmpty
Location: src/lib/utils.ts
Purpose: Checks if a form entry is completely empty.
Signature:
function isEntryEmpty(entry: ESFormEntry): boolean
Returns: true if all fields are empty after trim, false otherwise
Use Case: Determines if entry should be ignored or reset