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
- Basic Skill Addition: Search for a skill and click to add
- Enter Key Addition: Type skill name and press Enter
- Custom Skills: Add skills not in database
- Proficiency Changes: Use sliders to adjust skill levels
- Skill Removal: Click X button to remove skills
- 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