KKiotaPay UI
Browse documentation

User Selector

An employee picker built on Select. Every instance reads the same cached query, so a form with five approver fields fetches the directory once.

Installation

pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/user-selector.json

Installs the component, select and form-field, and adds @tanstack/react-query.

Setup

Needs a QueryClientProvider above it, and a user source:

'use client'

import { UserDirectoryProvider } from '@/components/custom/user-selector'
import { GetFeederUsers } from '@/lib/api/auth-endpoints'

export function Providers({ children }) {
  return <UserDirectoryProvider fetchUsers={GetFeederUsers}>{children}</UserDirectoryProvider>
}

fetchUsers may resolve to a bare array or an enveloped { data: [...] }.

Usage

'use client'

import { UserSelector } from '@/components/custom/user-selector'

export function ApprovalChain({ approver, reviewer, setApprover, setReviewer }) {
  return (
    <>
      <UserSelector label="Approver" required value={approver} onChange={setApprover} />
      <UserSelector
        label="Reviewer"
        value={reviewer}
        onChange={setReviewer}
        excludeIds={approver ? [approver] : []}
      />
    </>
  )
}

Props

PropTypeDefaultDescription
valuestring—Selected user id.
onChange(userId: string) => void—Required.
fetchUsersFetchUsers—Overrides the provider for this instance.
labelstring'Select User'Field label.
excludeIdsstring[]—Users to leave out.
hideCodebooleanfalseDrops the employee number from the label.
activeOnlybooleantrueHides non-ACTIVE users.
allOption{ value: string; label: string }—Prepends a sentinel entry.
staleTimenumber300000Cache lifetime, in milliseconds.
required / error / errorMessagePassed through to Select.

Behaviour notes

The current value survives every filter. A user who is excluded, or has since been deactivated, is still shown while selected — hiding them would leave the field blank where a real answer exists. Deactivated users keep a — SUSPENDED suffix so the state is visible rather than implied.

The "All users" entry is opt-in. Pass allOption={{ value: 'all', label: 'All Users' }} where a filter needs it. A picker that silently injects a pseudo-user into every list is a surprise the one form that wanted it does not justify.

One query key for the whole app. All instances share ['kiota-user-directory'], which is what makes the shared cache work — and what means staleTime should be set consistently.