-
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
Fix: Blank screen after creating an account #2575
Fix: Blank screen after creating an account #2575
Conversation
Wait for the NavigationContainer and all its children finish mounting for the first time before allowing/performing navigation If there were navigation attempts - the last navigation attempt would be automatically applied when the navigation is enabled
…alReportID Sometimes `currentReportID` might resolve as empty '' and prevent the RootStack navigator from mounting `const currentReportID = firstReportID ? String(firstReportID) : '';` https://github.com/Expensify/Expensify.cash/blob/0da357c30bc1ace6d354a81aabe075f87c12144c/src/libs/actions/Report.js#L688
…hen there are no reports When `response.chatList` is an empty array (new users) - `reportIDs = String(response.chatList).split(',');` Results in `reportIDs` being equal to `['']` and the former control flow `if (reportIDs.length)` evaluated to `true` Not casting the chat list IDs to string is not longer an issue because all the following usages are working with strings as well as with numbers
…tReport` is not used in `reportIDs`
…no router is mounted
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 it might be better to remove the conditional rendering in AuthScreens
:
And let the AuthScreens
mount instantly so that stack navigators can swap instantly and a navigator is always available
This would also remove the code inside shouldComponentUpdate
:
When the app initializes from a stored state it would still load the persisted report
If no report is available in route params by the time the ReportScreen
is mounted it can request navigation to transition to the first available report in the chat list
This way most of the changes in this PR, but the bug fixes, will be removed
src/libs/actions/Report.js
Outdated
@@ -950,7 +951,7 @@ function handleReportChanged(report) { | |||
* @param {Number} reportID | |||
*/ | |||
function updateCurrentlyViewedReportID(reportID) { | |||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID)); | |||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID || '')); |
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've updated this so it can be reused in fetchAllReports
and to guard against null
, undefined
as String(null)
would create a "null"
(similar situation for undefined and NaN)
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.
Hmm, this is the one thing that I'm not so sure about. If we are calling this with a reportID
that is null
or undefined
why try to set it at all? Couldn't we just leave whatever existing value we have? Seems not ideal to fail silently and set the currentlyViewedReportID
to an empty string. Maybe it would be appropriate to log an alert of some kind when this happens instead?
We have some ways to do that e.g.
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.
Hmm, this is the one thing that I'm not so sure about. If we are calling this with a reportID that is null or undefined why try to set it at all?
As you can see the original logic would save anything without any consideration, I've just updated it so that if called with null or undefined they are converted
I'll just revert this, this will also address the used-before-defined problem
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.
Yes, I see. Only mentioning this since logging an alert would perhaps be useful. But no need to add it in this PR.
src/libs/actions/Report.js
Outdated
const currentReportID = firstReportID ? String(firstReportID) : ''; | ||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, currentReportID); | ||
// eslint-disable-next-line no-use-before-define | ||
updateCurrentlyViewedReportID(firstReportID); |
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 I should move updateCurrentlyViewedReportID
to be declared before this usage so the eslint override can be removed.
About that eslint rule it can be tweaked. There's nothing wrong using a function
before it's defined and the rule allows for such configuration. The problem is when you try to use a class
or a variable before they are defined.
This style rule is forcing specifics to be defined at the top, which usually degrades readability.
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.
About that eslint rule it can be tweaked.
It has been brought up before. We generally agree with the reasoning laid out here
Updated |
Thanks, going to let @thienlnam take a first pass at reviewing + testing this then I will jump in. |
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.
Changes look good, just a small comment and then going to give it a test
I've added sample videos for all platforms: I don't know how you test deep links on iOS / Android. This is what I do for iOS for example: iOS.Deep.Link.mp4
Maybe something about it can be included in the README |
In the event of an error a currently viewed report would never get set This would result in a invalid initial state of the app Moving the logic to execute in `finally` would cover both success and error cases and allow the app to continue and recover
…lved The Report Screen is setup to mount when: - routing information is already available - initial params provide fallback when routing information is missing This would keep the Report Screen mounted in case the `initialReportID` is ever reset to `null` like in the event of clearing Onyx data
Updated |
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.
Nothing in this PR is a major blocker for me so I am leaning towards merging so we can unblock the deploy and do a follow up to address any comments. cc @roryabraham
src/libs/actions/Report.js
Outdated
const currentReportID = firstReportID ? String(firstReportID) : ''; | ||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, currentReportID); | ||
// eslint-disable-next-line no-use-before-define | ||
updateCurrentlyViewedReportID(firstReportID); |
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.
About that eslint rule it can be tweaked.
It has been brought up before. We generally agree with the reasoning laid out here
src/libs/actions/Report.js
Outdated
@@ -950,7 +951,7 @@ function handleReportChanged(report) { | |||
* @param {Number} reportID | |||
*/ | |||
function updateCurrentlyViewedReportID(reportID) { | |||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID)); | |||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID || '')); |
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.
Hmm, this is the one thing that I'm not so sure about. If we are calling this with a reportID
that is null
or undefined
why try to set it at all? Couldn't we just leave whatever existing value we have? Seems not ideal to fail silently and set the currentlyViewedReportID
to an empty string. Maybe it would be appropriate to log an alert of some kind when this happens instead?
We have some ways to do that e.g.
src/libs/actions/Report.js
Outdated
@@ -950,7 +951,7 @@ function handleReportChanged(report) { | |||
* @param {Number} reportID | |||
*/ | |||
function updateCurrentlyViewedReportID(reportID) { | |||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID)); | |||
Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID || '')); |
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.
Hmm, this is the one thing that I'm not so sure about. If we are calling this with a reportID that is null or undefined why try to set it at all?
As you can see the original logic would save anything without any consideration, I've just updated it so that if called with null or undefined they are converted
I'll just revert this, this will also address the used-before-defined problem
…e.state` usage" This reverts commit a2aa335
…ts ready There's no point in mounting the actual navigator before it has anything to route Added a fallback logic here so that even if `initialReportID` is never set the component would still resolve and function correctly
This reverts commit f394b98
This handling is no longer needed Initial routing data in `MainDrawerNavigator` would resolve with a reportID Then the ReportScreen will store it in Onyx when it is mounted: didMount -> storeCurrentlyViewedReport
When `initialReportId` is not available we can fall back to the last viewed report. BTW `initialReportId` might not be needed at all since it would be storing the last accessed/viewed report
d53f945
to
52a3bea
Compare
When `initialReportId` is not available we can fall back to the last viewed report. BTW `initialReportId` might not be needed at all since it would be storing the last accessed/viewed report
52a3bea
to
f0050f6
Compare
`initialReportID` is always the same as the ID returned by `getLastAccessedReport`
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.
With these changes ONYXKEYS.CURRENTLY_VIEWED_REPORTID
is no longer needed for initialization purposes.
…ve to navigate to it This would be resolved by initial params Otherwise it still work, but an error is printed out that navigation failed The ReportScreen is not mounted as the change that mounts it happens a few lines before the call to navigate in `fetchOrCreateChatReport`
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.
Nothing glaring is pointing out to me, let's get this deploy out - sending last review to you @marcaaron
@kidroca, I noticed that you had one approval, and then made substantial changes to this PR. I don't have all the context for why those changes were made, but I did just want to point out that this PR fixes a deploy-blocking issue that has been preventing us from running a production deploy for more than a week now. Generally with deploy blockers we want to get the quickest fix that solves the problem at hand so we're no longer holding up the deploy, even if you then create a follow-up PR to provide a better or more robust solution during the next deploy. Just something to keep in mind for the future 🙂 |
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.
LGTM, I only have some suggestions about how to improve the documentation around some of the logic in the drawer navigator but no need to block.
// eslint-disable-next-line react/jsx-props-no-multi-spaces | ||
initialParams={{reportID: ''}} | ||
options={{ | ||
// Decorated to always returning the result of the first call - keeps Screen initialParams from changing |
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 comment is a little hard to follow and I might change it to something like:
We are decorating
getLastAccessedReport
so that it caches the first result once the reports load. This will ensure theinitialParams
are only set once for theReportScreen
.
Which also begs the question... what happens if the initialParams
change? And why do we care? Maybe we can explain that as well here just so it is very obvious?
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.
Nothing will happen if we pass different initialParams
after the screen is mounted. And that's why we want to stop calculating them each time MainDrawerNavigator
re-renders
It might be better to just remove this _.once
optimization and address it if it ever becomes a problem
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 one, yep I think this is fine in any case but took me a second to get why so a small comment would have been helpful.
@roryabraham Since it wasn't merged and no one said nothing. I looked at the comments left by Marc and came up with an even more brilliant solution and had the time to implement it. Besides I've left a good description which should have helped reviewing them quickly |
🚀 Deployed to staging in version: 1.0.33-4🚀
|
Just a heads up there is an issue with this implementation and it's possible to get into a bad state if you have some partial report data. Here's what I've observed: Which, as you can imagine, leads to some pretty broken stuff 😄 That said, I was only able to reproduce this a few times. Still we should be careful to avoid this case and not present any report that has no |
Nice catch @marcaaron I didn't know that there could be such a report in the collection I see that this is handled with a I don't know why incomplete reports aren't stored in a different collection but perhaps we can extract this check to function isIncompleteReport(report) {
if (!report || !report.reportID) return true;
const participants = lodashGet(report, ['participants'], []);
return _.isEmpty(logins);
} Then update the code here: To account for that function getLastAccessedReport(reports) {
return _.chain(reports)
.toArray()
.filter(report => isIncompleteReport(report) == false)
.sortBy('lastVisitedTimestamp')
.last()
.value();
} I can't find how a partial report is created to analyze this further, can you point me to when/where this happens? Added a patch here: #2649 |
Yep, perfect plan! I don't quite understand how they are created. It could be a race condition or a failed network request or something else entirely. |
🚀 Deployed to production in version: 1.0.39-5🚀
|
<If necessary, assign reviewers that know the area or changes well. Feel free to tag any additional reviewers you see fit.>
@marcaaron
Details
Address the problem that no navigator is available during application initialization
We don't need to keep the entire AuthScreens navigator unmounted but just the Report screen
This way the URL still resolves to the correct report after the data is available
Fixed Issues
Fixes #2497
Tests
/r
until the initial report number is resolved/loading
but we decided to change itPlease check the steps listed in the PR here #2346 work as well and there's no regressions
QA Steps
Same as above
Tested On
Screenshots
Web
I'm intentionally setting a slow internet connection to test that the URL (chat) would resolve correctly even when the data comes after the App's initial render
Creating account over a slow internet connection
Expensify.cash.Web.-.Slow.Create.Account.-.Report.And.URL.still.resolve.mp4
Login in over a slow internet connection
Expensify.cash.Web.-.Slow.Login.-.Report.and.URL.still.resolves.mp4
Mobile Web
Logging in on mobile web
Screen.Recording.2021-04-27.at.22.41.33.mov
Desktop
Logging in on the desktop app
Screen.Recording.2021-04-27.at.23.11.10.mov
iOS
A simulated login using the magic link after an account is created
Simulate a magic link to set a password for a new account
iOS.Deep.Link.mp4
Android
Testing on Android to verify everything still works as before
Expensify.cash.Android.-.Sign.In.mp4