-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Update Style Guide on Hooks #16323
Update Style Guide on Hooks #16323
Conversation
@@ -413,63 +411,7 @@ const propTypes = { | |||
}; | |||
``` | |||
|
|||
* Avoid public methods on components and calling them via refs |
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 defensive comment is largely irrelevant if we are removing class components. The way to do this with hooks would be useImperativeHandle()
, but it's kind of a rare edge case that we would reach for it at all. And generally much harder to abuse this without knowing that it's an option.
@@ -540,96 +482,48 @@ const propTypes = { | |||
} | |||
``` | |||
|
|||
## Binding methods |
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 class specific and totally unnecessary in function components.
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.
yay, stoked that we won't have to bind
so much everywhere.
However, maybe some guidelines on when a function should be declared inside the component vs outside would be helpful?
// Outside
function doSomething() {
console.log('Hello world');
}
const MyComponent = () => {
// Inside
const doSomethingElse = () => {
console.log('Goodnight, moon');
}
return (
<View>
<Button onPress={doSomething}>
Hello world
</Button>
<Button onPress={doSomethingElse}>
Goodnight moon
</Button>
</View>
);
};
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.
Yeah, I agree. So, this is kind of an interesting question because we have the jsx-no-bind
rule enabled which will warn if you have any function in a function component that is not using useCallback()
. But I also don't think that there is always a good reason to use useCallback()
(even if your function has dependencies sometimes it might make sense to redeclare the function?).
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 think I would come back and update this later as part of some evolving best practices. My instinct is to say something like...
If you aren't going to memoize the function with useCallback()
then make it a pure function and move it outside the component if you can. If you need to create a closure around the props or state or something else then either allow it to be redeclared or just useCallback()
. Probably the community has some idea about what would be best.
@@ -684,125 +574,9 @@ When writing a stateless component you must ALWAYS add a `displayName` property | |||
|
|||
## Stateless components vs Pure Components vs Class based components vs Render Props - When to use what? | |||
|
|||
*_1. Stateless components: Used when you don't need to maintain state or use lifecycle methods._* | |||
|
|||
In many cases, we create components that do not need to have a state, lifecycle hooks or any internal variables. In other words just a simple component that takes props and renders something presentational. But often times we write them as class based components which come with a lot of cruft (for e.g., it has a state, lifecycle hooks and it is a javascript class which means that React creates instances of it) that is unnecessary in many cases. |
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 section is irrelevant because function components now can have "state" so they are not "stateless"
... | ||
|
||
``` | ||
*_2. Pure components: Use to improve performance where a component does not need to be rendered too often._* |
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 whole section is a bit out of date though I suppose we can mention something about performance. React.memo()
is basically the replacement for any PureComponent
or shouldComponentUpdate()
.
|
||
Let's say you have two separate components that render two different things, but you want them both to be based on the same state. You can pass a `render` property to a component or pass a function as a child to accomplish this. You can read more about how this works and when to use it in the [React Docs](https://reactjs.org/docs/render-props.html). | ||
|
||
## Do not use inheritance for other React components |
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.
There isn't any concern here since we won't have any class components. Hooks are designed to facilitate code reuse so while there may still be HOCs render props are going to be a less attractive pattern than hooks.
} | ||
``` | ||
|
||
## isMounted is an Antipattern |
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.
The cleanup method of useEffect()
should solve any issues with this section.
…e do not need to memoize a function?
@sobitneupane @danieldoglas One of you needs to 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] |
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.
Overall nothing here looks concerning, the changes looks pretty good. It's exciting how much we can remove from our style guide. Seems consistent with our hope that switching to hooks will reduce "gotchas" for NewDot contributors.
@@ -540,96 +482,48 @@ const propTypes = { | |||
} | |||
``` | |||
|
|||
## Binding methods |
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.
yay, stoked that we won't have to bind
so much everywhere.
However, maybe some guidelines on when a function should be declared inside the component vs outside would be helpful?
// Outside
function doSomething() {
console.log('Hello world');
}
const MyComponent = () => {
// Inside
const doSomethingElse = () => {
console.log('Goodnight, moon');
}
return (
<View>
<Button onPress={doSomething}>
Hello world
</Button>
<Button onPress={doSomethingElse}>
Goodnight moon
</Button>
</View>
);
};
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 noticed there are a couple items on the PR checklist:
- For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
- Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
Should we remove these? Or not yet?
Edit: Maybe we shouldn't remove those until we don't have any more class components.
shouldShow={_.isEmpty(this.props.policy)} | ||
}, [props.reports, props.policy]); | ||
|
||
const policy = props.policy; |
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.
Since you're creating a local variable for policy
, let's move it up and use it on line 72 where we're currently using props.policy
?
Reviewer Checklist
Screenshots/VideosWebWeb.movMobile Web - ChromeAndroidmWeb.movMobile Web - SafariRPReplay_Final1680093923.MP4Desktopdesktop.moviOSiOS.movAndroidAndroid.mov |
Just noting that I found a bug but was able to reproduce it on production. Reported here. Also added some offline test steps. |
Going to go ahead and merge this. We can always adjust the style guide more if needed. |
✋ 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/roryabraham in version: 1.2.92-0 🚀
|
}, | ||
onExit: (exitError, metadata) => { | ||
Log.info('[PlaidLink] Exit: ', false, {exitError, metadata}); | ||
props.onExit(); | ||
propsRef.current.onExit(); | ||
}, | ||
}); | ||
}, []); |
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 don't need to use useRef
here.
useEffect(() => {}, []
) - empty array dependency - this is equivalent to componentDidMount
.
So openLink
is called only once regardless of props.token
change.
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.
interesting. but does it cause any harm? if we use the props.token
directly then the exhaustive-deps
linter would complain about a missing dependency.
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.
if it complains, we can disable with a comment instead of using ref: https://expensify.slack.com/archives/C01GTK53T8Q/p1680256754764159
import {openLink, useDeepLinkRedirector, usePlaidEmitter} from 'react-native-plaid-link-sdk'; | ||
import Log from '../../libs/Log'; | ||
import CONST from '../../CONST'; | ||
import {plaidLinkPropTypes, plaidLinkDefaultProps} from './plaidLinkPropTypes'; | ||
|
||
const PlaidLink = (props) => { | ||
// We are stashing initial props in a ref since we don't want a new token to trigger the link to open again | ||
// and just want openLink() to be called once | ||
const propsRef = useRef(props.token); |
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.
cc @marcaaron I think this should hold all the props not only the token
const propsRef = useRef(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.
Oh, yes, I see my mistake now
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.
Thank you
🚀 Deployed to production by https://github.com/luacmartins in version: 1.2.92-2 🚀
|
Details
Fixed Issues
$ #16325
Tests
Offline tests
N/A
QA Steps
Same as tests
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)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
properly so there are no scoping issues (i.e. foronClick={this.submit}
the methodthis.submit
should be bound tothis
in the constructor)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
Web
Mobile Web - Chrome
Mobile Web - Safari
Desktop
iOS
Android