Skip to main content

Skills Selector State Management Fix

Issue

The skills selector was not properly retaining selected skills in the list, and users couldn't change proficiency levels. The skills would appear to be added but then disappear or not show up consistently.

Root Cause Analysis

Several issues were identified in the SkillsSelector component:

1. Unstable AutocompleteItem Keys

// BEFORE - Keys included index, causing instability
{skillOptions.map((skill, index) => (
<AutocompleteItem key={`${skill.id}-${index}`}>
{skill.name}
</AutocompleteItem>
))}

Problem: When skills are added/removed, the index changes, causing React to re-render components incorrectly and lose selection state.

2. Missing selectedKey State Management

// BEFORE - No selectedKey control
<Autocomplete
inputValue={inputValue}
onInputChange={setInputValue}
onSelectionChange={(key) => { ... }}
/>

Problem: The Autocomplete component wasn't properly controlled, leading to inconsistent selection behavior.

3. Stale Closure in useCallback

// BEFORE - skills dependency caused stale closures
const addSkill = useCallback((skill: Skill) => {
if (skills.some((s) => s.id === skill.id)) return
// ...
}, [skills, setSkills]) // ❌ skills dependency

Problem: The skills dependency in useCallback created stale closures that referenced outdated skill arrays.

4. Invalid AutocompleteItem Props

// BEFORE - Invalid value prop
<AutocompleteItem key={skill.id} value={skill.id}>
{skill.name}
</AutocompleteItem>

Problem: HeroUI's AutocompleteItem doesn't accept a value prop, causing TypeScript errors.

Solution Implementation

1. Stable Keys

// AFTER - Use stable skill.id as key
{skillOptions.map((skill) => (
<AutocompleteItem key={skill.id}>
{skill.name}
</AutocompleteItem>
))}

2. Controlled Selection State

// AFTER - Added selectedKey state and control
const [selectedKey, setSelectedKey] = useState<string | null>(null)

<Autocomplete
inputValue={inputValue}
selectedKey={selectedKey}
onInputChange={setInputValue}
onSelectionChange={(key) => {
if (key) {
const selected = skillOptions.find((s) => s.id === key)
if (selected) {
addSkill(selected)
}
}
}}
/>

3. Fixed Stale Closure

// AFTER - Removed skills dependency, use functional update
const addSkill = useCallback(
(skill: Skill) => {
setSkills((prev) => {
// Check if skill already exists using current state
if (prev.some((s) => s.id === skill.id)) return prev

const newSkill: SkillItem = {
id: skill.id,
name: skill.name,
level: 0,
proficiency: "NONE",
weight: 100,
mustHave: true,
yearsExperience: 0,
}
return [...prev, newSkill]
})
setInputValue("")
setSelectedKey(null) // Clear selection after adding
},
[setSkills] // ✅ Only setSkills dependency
)

4. Proper Type Safety

// AFTER - Explicit type annotation and proper proficiency type
const newSkill: SkillItem = {
id: skill.id,
name: skill.name,
level: 0,
proficiency: "NONE",
weight: 100,
mustHave: true,
yearsExperience: 0,
}

Additional Improvements

State Reset on Skill Addition

  • Input value clears after adding skill: setInputValue("")
  • Selection state resets: setSelectedKey(null)
  • Prevents duplicate additions with functional state update

Enhanced Enter Key Handling

The Enter key handling already supports:

  • Adding existing skills from search results
  • Creating custom skills that don't exist in database
  • Proper input clearing after addition

Proficiency Management

The component correctly handles proficiency levels:

  • UI Level (0-4): Amateur, Basic, Intermediate, Advanced, Expert
  • Backend Proficiency: NONE, BASIC, INTERMEDIATE, ADVANCED, EXPERT
  • Proper Mapping: Uses mapLevelToProficiency() for conversion

Testing Steps

  1. Basic Skill Addition: Search for a skill and click to add
  2. Enter Key Addition: Type skill name and press Enter
  3. Custom Skills: Add skills not in database
  4. Proficiency Changes: Use sliders to adjust skill levels
  5. Skill Removal: Click X button to remove skills
  6. Navigation Persistence: Switch between form steps and verify skills remain

Files Modified

  • components/reusable/jobs/SkillsSelector.tsx - Fixed state management and selection behavior

Result

Skills persist in list after selection
Proficiency sliders work correctly
No duplicate skill additions
Stable component rendering
Proper TypeScript compilation
Enter key and click selection both work
Custom skills can be added
Skills remain when navigating between form steps