[TS migration] Migrate 'Button' component to TypeScript#31102
[TS migration] Migrate 'Button' component to TypeScript#31102srikarparsi merged 22 commits intoExpensify:mainfrom
Conversation
| /** Any additional styles to pass to the left icon container. */ | ||
| // eslint-disable-next-line react/forbid-prop-types | ||
| iconStyles: PropTypes.arrayOf(PropTypes.object), | ||
| iconStyles?: Array<StyleProp<ViewStyle>>; |
There was a problem hiding this comment.
Just StyleProp<ViewStyle> should be enough, then in the code replace ...iconStyles with just iconStyles, it should work the same way as before
| iconStyles?: Array<StyleProp<ViewStyle>>; | |
| iconStyles?: StyleProp<ViewStyle>; |
| /** Any additional styles to pass to the right icon container. */ | ||
| // eslint-disable-next-line react/forbid-prop-types | ||
| iconRightStyles: PropTypes.arrayOf(PropTypes.object), | ||
| iconRightStyles?: Array<StyleProp<ViewStyle>>; |
There was a problem hiding this comment.
Same
| iconRightStyles?: Array<StyleProp<ViewStyle>>; | |
| iconRightStyles?: StyleProp<ViewStyle>; |
|
|
||
| /** A function that is called when the button is clicked on */ | ||
| onPress: PropTypes.func, | ||
| onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void; |
There was a problem hiding this comment.
| onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void; | |
| onPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; |
|
|
||
| /** A function that is called when the button is long pressed */ | ||
| onLongPress: PropTypes.func, | ||
| onLongPress?: (e?: GestureResponderEvent) => void; |
There was a problem hiding this comment.
| onLongPress?: (e?: GestureResponderEvent) => void; | |
| onLongPress?: (event?: GestureResponderEvent) => void; |
|
|
||
| /** Additional styles to add after local styles. Applied to Pressable portion of button */ | ||
| style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), | ||
| style?: ViewStyle | ViewStyle[]; |
There was a problem hiding this comment.
Why not StyleProp<ViewStyle>?
There was a problem hiding this comment.
It looks a little bit strange. How do you think can we use just use style instead of ...StyleUtils.parseStyleAsArray(style)?
There was a problem hiding this comment.
I agree, let's just use style, parseStyleAsArray isn't needed
| isDisabled && !danger && !success ? styles.buttonDisabled : undefined, | ||
| shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, | ||
| shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, | ||
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing |
There was a problem hiding this comment.
Why not nullish coalescing in this place?
There was a problem hiding this comment.
icon || shouldShowRightIcon ? styles.alignItemsStretch : undefined
i gues for if statments we should use normal OR operator
There was a problem hiding this comment.
You can try !!icon || shouldShowRightIcon...., eslint should pass it so you can remove eslint disable
| ButtonWithRef.displayName = 'ButtonWithRef'; | ||
|
|
||
| export default withNavigationFallback(ButtonWithRef); | ||
| export default withNavigationFallback(React.forwardRef(ButtonWithRef)); |
There was a problem hiding this comment.
If you add ref as a second param you can do it this way
| export default withNavigationFallback(React.forwardRef(ButtonWithRef)); | |
| export default withNavigationFallback(React.forwardRef(Button)); |
| @@ -282,7 +242,8 @@ function Button({ | |||
| ref={forwardedRef} | |||
| onPress={(event) => { | |||
| if (event && event.type === 'click') { | |||
There was a problem hiding this comment.
| if (event && event.type === 'click') { | |
| if (event?.type === 'click') { |
| onPress={(event) => { | ||
| if (event && event.type === 'click') { | ||
| event.currentTarget.blur(); | ||
| const currentTarget = event?.currentTarget as HTMLElement; |
There was a problem hiding this comment.
Why we need assertion here?
There was a problem hiding this comment.
without assertion ts thinks that currentTarget is type of number | EventTarget on which blur function is not existing but we can assert that its HTMLElement because click event type is only on web or desktop
| */ | ||
|
|
||
| const validateSubmitShortcut: ValidateSubmitShortcut = (isFocused, isDisabled, isLoading, event) => { | ||
| const eventTarget = event?.target as HTMLElement; |
There was a problem hiding this comment.
Same question about assertion :)
| ButtonWithRef.displayName = 'ButtonWithRef'; | ||
|
|
||
| export default withNavigationFallback(ButtonWithRef); | ||
| export default withNavigationFallback(React.forwardRef(Button)); |
There was a problem hiding this comment.
Can we remove withNavigationFallback now? And use navigation hook instead @VickyStash
There was a problem hiding this comment.
@blazejkustra It looks like it should be possible, @kubabutkiewicz is going to give it a try
|
|
||
| /** Additional styles to add after local styles. Applied to Pressable portion of button */ | ||
| style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), | ||
| style?: ViewStyle | ViewStyle[]; |
There was a problem hiding this comment.
I agree, let's just use style, parseStyleAsArray isn't needed
| import validateSubmitShortcut from './validateSubmitShortcut'; | ||
|
|
||
| const propTypes = { | ||
| type ButtonProps = { |
There was a problem hiding this comment.
I think there are two ways of using a Button, either with children or with text and icons:
import ChildrenProps from '@src/types/utils/ChildrenProps';
type ButtonWithText = {
/** The text for the button label */
text: string;
/** Boolean whether to display the right icon */
shouldShowRightIcon?: boolean;
/** The icon asset to display to the left of the text */
icon?: React.FC<SvgProps> | null;
};
type ButtonProps = (ButtonWithText | ChildrenProps) & { ... }There was a problem hiding this comment.
This makes destructing a little bit more tricky:
function Button(
{
allowBubble = false,
iconRight = Expensicons.ArrowRight,
iconFill = themeColors.textLight,
iconStyles = [],
iconRightStyles = [],
small = false,
large = false,
medium = false,
isLoading = false,
isDisabled = false,
onPress = () => {},
onLongPress = () => {},
onPressIn = () => {},
onPressOut = () => {},
onMouseDown = undefined,
pressOnEnter = false,
enterKeyEventListenerPriority = 0,
style = [],
innerStyles = [],
textStyles = [],
shouldUseDefaultHover = true,
success = false,
danger = false,
shouldRemoveRightBorderRadius = false,
shouldRemoveLeftBorderRadius = false,
shouldEnableHapticFeedback = false,
id = '',
accessibilityLabel = '',
...rest
}: ButtonProps,
ref: ForwardedRef<View>,
) { const renderContent = () => {
if ('children' in rest) {
return rest.children;
}
const {text = '', icon = null, shouldShowRightIcon = false} = rest;|
@allroundexperts 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] |
…s-migration/Button/component
…s-migration/Button/component
|
@allroundexperts kind bump 😄 |
| danger && styles.buttonDangerText, | ||
| icon && styles.textAlignLeft, | ||
| ...textStyles, | ||
| textStyles, |
There was a problem hiding this comment.
Why did you remove the ...?
There was a problem hiding this comment.
@allroundexperts
Actually spread operator is not needed here
we can pass array or object without spreading and styles will still work

| <View style={[styles.alignItemsCenter, styles.flexRow, styles.flexShrink1]}> | ||
| {icon && ( | ||
| <View style={[styles.mr1, ...iconStyles]}> | ||
| <View style={[styles.mr1, iconStyles]}> |
There was a problem hiding this comment.
Same. Why is ... being removed?
| </View> | ||
| {shouldShowRightIcon && ( | ||
| <View style={[styles.justifyContentCenter, styles.ml1, ...iconRightStyles]}> | ||
| <View style={[styles.justifyContentCenter, styles.ml1, iconRightStyles]}> |
| icon || shouldShowRightIcon ? styles.alignItemsStretch : undefined, | ||
| ...innerStyles, | ||
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | ||
| 'text' in rest && (rest?.icon || rest?.shouldShowRightIcon) ? styles.alignItemsStretch : undefined, |
There was a problem hiding this comment.
Not sure what you're trying to do here. Why 'text' in rest is being added? Why is it not using ??
There was a problem hiding this comment.
this is just for type narrowing , there are two ways of using Button component one with children like
<Button>Hello</Button
or with text props like
<Button text="Hello" />
and those two props icon and shouldShowRightIcon can be used only when Button is used with text prop. 'text' in rest will assure that Button is used with text prop
There was a problem hiding this comment.
about the ? its needed only when we are using object dot notation so if rest is undefined it will be safe to use properties of the object. when using 'text' in rest and rest is undefined it will just return false
| ButtonWithRef.displayName = 'ButtonWithRef'; | ||
|
|
||
| export default withNavigationFallback(ButtonWithRef); | ||
| export default React.forwardRef(Button); |
There was a problem hiding this comment.
Why is the withNavigationFallback being removed?
There was a problem hiding this comment.
instead of the withNavigationFallback we can use useIsFocused which we are doing , so withNavigationFallback is not needed anymore
…s-migration/Button/component
…s-migration/Button/component
|
@allroundexperts kind bump 😄 |
|
I have triggered a build for this PR, @allroundexperts i know you have lots on your plate, this is one of 2 most important/ most blocking TS migration PRs now, could we reassign to someone who can take this over the finish line as soon as possible? the transition bug you are working on for instance is higher priority though |
|
@kubabutkiewicz can you please check the reassure performance tests failing? this should not be happening on the migration PR |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! 🧪🧪 |
…s-migration/Button/component
…s-migration/Button/component
|
@mountiny @allroundexperts Alternatively @parasharrajat could take over as he has a lot of context |
|
Sorry, I missed the previous notifications for this. I have noted this down and will finish this today. |
…s-migration/Button/component
|
I can review this immediately, if needed. |
|
Thank you @parasharrajat. But I've started already. |
Reviewer Checklist
Screenshots/VideosAndroid: NativeScreen.Recording.2023-11-21.at.11.09.26.PM.movAndroid: mWeb ChromeScreen.Recording.2023-11-21.at.11.08.46.PM.moviOS: NativeScreen.Recording.2023-11-21.at.11.15.04.PM.moviOS: mWeb SafariScreen.Recording.2023-11-21.at.11.13.00.PM.movMacOS: Chrome / SafariScreen.Recording.2023-11-21.at.11.00.40.PM.movMacOS: DesktopScreen.Recording.2023-11-21.at.11.04.42.PM.mov |
allroundexperts
left a comment
There was a problem hiding this comment.
mweb screenshots are missing but since this is urgent, I'm approving this.
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #25098 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
…s-migration/Button/component
|
@srikarparsi got the review on this one @allroundexperts thanks for the review |
…s-migration/Button/component
…s-migration/Button/component
…s-migration/Button/component
|
@srikarparsi kindly bump 🙏 |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/srikarparsi in version: 1.4.4-0 🚀
|
|
🚀 Deployed to production by https://github.com/mountiny in version: 1.4.4-3 🚀
|

Details
Fixed Issues
$ #25098
Tests
Offline tests
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)myBool && <MyComponent />.src/languages/*files and using the translation methodWaiting for Copylabel for a copy review on the original GH to get the correct copy.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)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
Android: Native
android.mp4
Android: mWeb Chrome
iOS: Native
ios.mp4
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mp4
MacOS: Desktop
desktop.mp4