-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TS migration] Migrate TextLink to Typescript #30907
[TS migration] Migrate TextLink to Typescript #30907
Conversation
My bad @situchan! This shouldn't yet go to ready for review. This should be reviewed internally first and then cross reviewed by Callstack. I'll mark as ready for review once this is done. |
src/components/TextLink.tsx
Outdated
const TextLinkWithRef: FC<TextLinkProps> = React.forwardRef<RNText, TextLinkProps>((props, ref) => ( | ||
<TextLink | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
forwardedRef={ref} | ||
/> | ||
)); | ||
|
||
TextLinkWithRef.displayName = 'TextLinkWithRef'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this part
src/components/TextLink.tsx
Outdated
|
||
TextLinkWithRef.displayName = 'TextLinkWithRef'; | ||
|
||
export default TextLinkWithRef; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export default TextLinkWithRef; | |
export default forwardRef(TextLink); |
src/components/TextLink.tsx
Outdated
forwardedRef: ForwardedRef<RNText>; | ||
}; | ||
|
||
function TextLink({href, children, style, onPress, onMouseDown = (event) => event.preventDefault(), forwardedRef, ...props}: TextLinkProps) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to typescript guidelines forwardRef should be handled a bit differently. Please remove forwardedRef: ForwardedRef<RNText>;
prop and move it to the second parameter like this:
function TextLink({href, children, style, onPress, onMouseDown = (event) => event.preventDefault(), forwardedRef, ...props}: TextLinkProps) { | |
function TextLink({href, children, style, onPress, onMouseDown = (event) => event.preventDefault(), ...props}: TextLinkProps, ref: ForwardedRef<RNText>) { |
src/components/TextLink.tsx
Outdated
}; | ||
|
||
const openLinkOnEnterKey: KeyboardEventHandler = (event) => { | ||
event.preventDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why event.preventDefault();
was moved from openLink? I advise against logic changes in TS migrations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope there's no logic change in here. I couldn't call openLink(event)
from openLinkIfEnterKeyPressed
function because the event
would be of different type than what openLink
expects (KeyboardEvent
vs GestureResponderEvent
). So I removed event.preventDefault()
call from openLink
and now I don't need to pass any arguments to openLink()
because event.preventDefault()
is being called from openLinkOnTap()
and openLinkOnEnterKey
. LMK if that makes sense. The only logic change here is that I technically changed the order of when event.preventDefault()
is called in openLinkOnEnterKey
:
Is:
const openLinkOnEnterKey: KeyboardEventHandler = (event) => {
event.preventDefault();
if (event.key !== 'Enter') {
return;
}
openLink();
};
How it should be to be exactly the same as in .js
file:
const openLinkOnEnterKey: KeyboardEventHandler = (event) => {
if (event.key !== 'Enter') {
return;
}
event.preventDefault();
openLink();
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll move event.preventDefault()
below the early return.
|
||
type TextLinkProps = { | ||
/** Link to open in new tab */ | ||
href: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
href is optional
href: string; | |
href?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me it makes more sense to have href required for a LinkText component, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. FormAlertWrapper doesn't have href
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the code one of 'onPress' and 'href' should be defined:
const openLink = (event) => {
event.preventDefault();
if (props.onPress) {
props.onPress();
return;
}
Link.openExternalLink(props.href);
};
My proposal is to use discriminating union @situchan @MaciejSWM:
{ onPress: type; } | { href: type; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented. I tested and it throws an error as expected when both href
and onPress
are defined. Nice @blazejkustra
src/components/TextLink.tsx
Outdated
style?: TextStyle; | ||
|
||
/** Overwrites the default link behavior with a custom callback */ | ||
onPress?: () => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more accurate?
onPress?: () => void; | |
onPress?: (event?: GestureResponderEvent) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, onPress
doesn't need arguments:
const openLink = () => {
if (onPress) {
onPress();
return;
}
Link.openExternalLink(href);
};
src/components/TextLink.tsx
Outdated
ref={ref} | ||
suppressHighlighting | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
props.style
will overwrite former style={[styles.link, style]}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's how it used to be 🤷♂️ seems reasonable for some use cases. Do you want me to move style={[styles.link, style]}
below {...props}
@situchan ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you missed important line in current code:
App/src/components/TextLink.js
Line 40 in b44f49e
const rest = _.omit(props, _.keys(propTypes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh shoot, good call. Fixing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait... let's talk in slack because I'm now doing function TextLink({href, children, style, onPress, onMouseDown = (event) => event.preventDefault(), ...props}
so technically this is equal to const rest = _.omit(props, _.keys(propTypes));
@situchan I guess all of the comments are addressed, ready for your review again |
Lint failing. Please take off draft when ready |
Also, pull main |
src/components/TextLink.tsx
Outdated
|
||
type TextLinkProps = (LinkProps | PressProps) & { | ||
/** Text content child */ | ||
children: ReactElement; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use ChildrenProps
here from src/types/utils/ChildrenProps.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually after this comment it is irrelevant. You can fix it directly in TextProps
👍
type TextProps = RNTextProps &
ChildrenProps & {
/** The color of the text */
color?: string;
/** The size of the text */
fontSize?: number;
/** The alignment of the text */
textAlign?: 'left' | 'right' | 'auto' | 'center' | 'justify';
/** The family of the font to use */
family?: keyof typeof fontFamily;
};
src/components/TextLink.tsx
Outdated
onMouseDown?: MouseEventHandler; | ||
}; | ||
|
||
function TextLink({href, onPress, children, style, onMouseDown = (event) => event.preventDefault(), ...props}: TextLinkProps, ref: ForwardedRef<RNText>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function TextLink({href, onPress, children, style, onMouseDown = (event) => event.preventDefault(), ...props}: TextLinkProps, ref: ForwardedRef<RNText>) { | |
function TextLink({href, onPress, children, style, onMouseDown = (event) => event.preventDefault(), ...rest}: TextLinkProps, ref: ForwardedRef<RNText>) { |
src/components/TextLink.tsx
Outdated
ref={ref} | ||
suppressHighlighting | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@situchan friendly bump :) |
Seems like you forgot to deprecate TextLink.js |
@MaciejSWM friendly bump |
Thx for bump, ready now |
return; | ||
} | ||
|
||
Link.openLink(props.href, environmentURL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for deprecating environmentURL
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please apply changes from #30664
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restored
Please pull main. |
@situchan main pulled. Testing steps updated. |
Reviewer Checklist
Screenshots/VideosAndroid: Nativeandroid.movAndroid: mWeb Chromemchrome.moviOS: Nativeios.moviOS: mWeb Safarimsafari.movMacOS: Chrome / Safariweb.movMacOS: Desktopdesktop.mov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did not find an internal engineer to review this PR, trying to assign a random engineer to #25023 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
✋ 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/jasperhuangg in version: 1.4.10-0 🚀
|
🚀 Deployed to staging by https://github.com/jasperhuangg in version: 1.4.10-0 🚀
|
🚀 Deployed to production by https://github.com/Julesssss in version: 1.4.10-1 🚀
|
🚀 Deployed to production by https://github.com/Julesssss in version: 1.4.10-1 🚀
|
Details
The component that is being migrated is marked in red:
Fixed Issues
$ #25023
PROPOSAL:
Tests
Verify that no errors appear in the JS console
Verify that you can click on the link with your mouse and with a finger and that it opens the link
Verify that you can highlight the link and then hit Enter and that it opens the link
Do the steps above with the following component that uses
href
on the login page:By logging in, you agree to the[ Terms of Service ](https://use.expensify.com/terms)and[ Privacy](https://use.expensify.com/privacy).
Do the steps above with the following component that uses
onPress
in Adding a Card screenPlease fix the errors in the form before continuing.
- on press it should move focus to the first input field that threw an errorOffline tests
You should still be able to open the link even in offline mode.
QA Steps
Tests
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodWaiting for Copy
label for a copy review on the original GH to get the correct copy.STYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)/** comment above it */
this
are necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);
ifthis.submit
is never passed to a component event handler likeonClick
)StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
Screen.Recording.2023-11-06.at.19.38.56.mov
Android: mWeb Chrome
Screen.Recording.2023-11-06.at.19.01.27.mov
iOS: Native
Screen.Recording.2023-11-06.at.19.20.39.mov
iOS: mWeb Safari
Screen.Recording.2023-11-06.at.19.01.27.mov
MacOS: Chrome / Safari
Screen.Recording.2023-11-06.at.18.48.11.mov
MacOS: Desktop
Screen.Recording.2023-11-06.at.19.01.51.mov