[NoQA] Add AvatarSelector component#72214
Conversation
|
@stitesExpensify Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
Looking good! Any way to organize these by color? I think that's what @dannymcclain had in mind at least. |
Codecov Report❌ Patch coverage is
... and 169 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Yeah that would be ideaaaal! |
|
Looks good. But we'll implement this selector in the avatar page, right? Screen.Recording.2025-10-10.at.00.07.14.mov |
|
@dukenv0307 the idea was to implement just the component in this task and integrate it in #71890 to keep PRs small and your work easier :) I'm gonna be working on integration today cc. @grgia |
grgia
left a comment
There was a problem hiding this comment.
@dukenv0307 That's correct, we will add them in the next issue. The plan is to only add the component in this PR. Mostly because I want to keep the release PRs small 😁
@jmusial I DMd you a diff for the avatar order, we could add that in this PR if you'd like
@dannymcclain already on it, here's the diff for that |
| const styles = useThemeStyles(); | ||
| const StyleUtils = useStyleUtils(); | ||
| const [selected, setSelected] = useState(selectedID); | ||
|
|
There was a problem hiding this comment.
❌ PERF-4
Object.entries(ALL_CUSTOM_AVATARS) creates a new array instance on every render. Since this array is used in the render loop below, React cannot optimize re-renders effectively as the reference changes every time.
Suggested fix:
const avatars = useMemo(() => Object.entries(ALL_CUSTOM_AVATARS), []);Since ALL_CUSTOM_AVATARS is a constant catalog, the dependency array can be empty.
| <> | ||
| {!!label && ( | ||
| <View style={[styles.pt5, styles.ph2]}> | ||
| <Text style={StyleUtils.combineStyles([styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting, styles.pre])}>{label}</Text> |
There was a problem hiding this comment.
❌ PERF-4
StyleUtils.combineStyles([...]) creates a new array on every render and combines it into a new style object. This new object reference causes unnecessary re-renders of the Text component.
Suggested fix:
const labelStyle = useMemo(
() => StyleUtils.combineStyles([styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting, styles.pre]),
[StyleUtils, styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting, styles.pre]
);
// Then use it:
<Text style={labelStyle}>{label}</Text>Or simply use array syntax directly without combineStyles:
<Text style={[styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting, styles.pre]}>{label}</Text>| accessible | ||
| accessibilityRole="button" | ||
| accessibilityLabel="Select Avatar" | ||
| onPress={() => handleSelect(id as keyof typeof ALL_CUSTOM_AVATARS)} |
There was a problem hiding this comment.
❌ PERF-4
This code creates a new style array and a new style object ({borderColor: theme.success, borderWidth: 2}) on every iteration of the map loop. Since this is rendering potentially dozens of avatars, this creates significant memory churn and prevents React from optimizing re-renders.
Suggested fix:
Move the selected border style outside the map:
const selectedBorderStyle = useMemo(
() => ({borderColor: theme.success, borderWidth: 2}),
[theme.success]
);
// Then in the map:
return (
<PressableWithFeedback
key={id}
accessible
accessibilityRole="button"
accessibilityLabel="Select Avatar"
onPress={() => handleSelect(id as keyof typeof ALL_CUSTOM_AVATARS)}
style={[styles.avatarSelectorWrapper, isSelected && selectedBorderStyle]}
>| 'wrenches-pink600': {local: SeasonF1.WrenchesPink600, url: `${CDN_SEASON_F1}/wrenches-pink600.png`}, | ||
| }; | ||
|
|
||
| const DISPLAY_ORDER = [ |
There was a problem hiding this comment.
https://github.com/Expensify/App/pull/72331/files
I made some slight improvements in this PR- want to integrate those?
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / SafariScreen.Recording.2025-10-13.at.22.21.07.movMacOS: Desktop |
|
LGTM |
|
🚀 Deployed to staging by https://github.com/grgia in version: 9.2.31-0 🚀
|
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.2.31-2 🚀
|

Explanation of Change
This PR adds a new AvatarSelector to be used while implementing #71653.
Fixed Issues
$ #71886
PROPOSAL:
Tests
Offline tests
n/a
QA Steps
n/a
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Screen.Recording.2025-10-09.at.15.30.14.mov