feat(PM-4203): Filter by skills in completed profiles page#1530
feat(PM-4203): Filter by skills in completed profiles page#1530hentrymartin wants to merge 3 commits intodevfrom
Conversation
| queryParams.set('openToWork', 'false') | ||
| } | ||
|
|
||
| if (Array.isArray(skillIds) && skillIds.length > 0) { |
There was a problem hiding this comment.
[💡 maintainability]
Consider using skillIds.filter(Boolean) to remove falsy values before iterating. This simplifies the code by eliminating the need for the if (id) check inside the loop.
...customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx
Show resolved
Hide resolved
| value: String(skill.id), | ||
| })) | ||
| .filter((option: InputMultiselectOption) => !!option.value) | ||
| } catch { |
There was a problem hiding this comment.
[correctness]
Filtering options with !!option.value might not be sufficient if value can be a falsy but valid value (e.g., 0). Ensure that this filtering logic aligns with the expected data structure.
...customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx
Show resolved
Hide resolved
| const loadSkillOptions = async (query: string): Promise<InputMultiselectOption[]> => { | ||
| setSkillOptionsLoading(true) | ||
| try { | ||
| const baseUrl = `${EnvironmentConfig.API.V5}/standardized-skills` | ||
| const params = new URLSearchParams({ | ||
| size: '25', | ||
| }) | ||
| if (query && query.trim().length > 0) { | ||
| params.append('term', query.trim()) | ||
| } | ||
|
|
||
| const url = `${baseUrl}/skills/autocomplete?${params.toString()}` | ||
| const response: any = await xhrGetAsync(url) | ||
|
|
||
| const skills = Array.isArray(response) ? response : [] | ||
|
|
||
| return skills | ||
| .map((skill: any) => ({ | ||
| label: skill.name, | ||
| value: String(skill.id), | ||
| })) | ||
| .filter((option: InputMultiselectOption) => !!option.value) | ||
| } catch { | ||
| return [] | ||
| } finally { | ||
| setSkillOptionsLoading(false) | ||
| } | ||
| } |
There was a problem hiding this comment.
🔴 Unstable loadSkillOptions reference causes AsyncSelect to re-fetch default options on every render
loadSkillOptions is defined as a plain arrow function inside the component body (ProfileCompletionPage.tsx:44-71), so it gets a new reference on every render. This reference is passed as onFetchOptions to InputMultiselect, which forwards it as loadOptions to react-select/async's AsyncSelect (InputMultiselect.tsx:156). In react-select v5, the useAsync hook has loadOptions in the dependency array of the useEffect that loads default options when defaultOptions={true} (InputMultiselect.tsx:154). Every time the function identity changes, the effect re-runs and calls loadOptions(''), triggering a new API request to /standardized-skills/skills/autocomplete?size=25. This means every state change in the parent component (SWR data arriving, member skills loading, filter changes) causes an unnecessary network request. The existing codebase avoids this: EngagementFilters.tsx:79 wraps fetchCountryOptions in useCallback, and InputSkillSelector.tsx:23 defines fetchSkills at module level.
Prompt for agents
In src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx, wrap the loadSkillOptions function (lines 44-71) in useCallback with an empty dependency array (setSkillOptionsLoading is a stable state setter and EnvironmentConfig.API.V5 is a constant). This ensures the function reference remains stable across renders, preventing AsyncSelect from re-fetching default options on every render. Add useCallback to the imports from react on line 4. Example:
const loadSkillOptions = useCallback(async (query: string): Promise<InputMultiselectOption[]> => {
setSkillOptionsLoading(true)
try {
... (same body)
} catch {
return []
} finally {
setSkillOptionsLoading(false)
}
}, [])
Was this helpful? React with 👍 or 👎 to provide feedback.
Related JIRA Ticket:
https://topcoder.atlassian.net/browse/PM-4203
What's in this PR?