Closed lanitochka17 closed 8 months ago
Job added to Upwork: https://www.upwork.com/jobs/~0198c10fd73498b28a
Triggered auto assignment to @Christinadobrzyn (Bug
), see https://stackoverflow.com/c/expensify/questions/14418 for more details.
Platforms
in OP are β
)Triggered auto assignment to Contributor-plus team member for initial proposal review - @cubuspl42 (External
)
The number of transactions with receipts on the report preview doesn't update.
The transaction with a receipt is calculated here. https://github.com/Expensify/App/blob/b74be723548d45d47dde7fe29aff5eafe16b290f/src/components/ReportActionItem/ReportPreview.js#L144
In getTransactionWithReceipts, we get all transactions that are linked with the passed IOU report id. https://github.com/Expensify/App/blob/b74be723548d45d47dde7fe29aff5eafe16b290f/src/libs/ReportUtils.ts#L1898-L1901
However, it won't rerender when the transaction collection is updated (added/removed) because we aren't subscribing the component with the transaction collection. Instead, we use a different technique here by using onyxSubscribe
.
https://github.com/Expensify/App/blob/b74be723548d45d47dde7fe29aff5eafe16b290f/src/components/ReportActionItem/ReportPreview.js#L209-L229
(here is the reason why we do this)
If we want to keep the onyxSubscribe
, then we should change transactionsWithReceipts
to a state and update it inside onyxSubscribe
.
const [transactionsWithReceipts, setTransactionsWithReceipts] = useState(ReportUtils.getTransactionsWithReceipts(props.iouReportID));
// inside onyxSubscribe
setTransactionsWithReceipts(ReportUtils.getTransactionsWithReceipts(props.iouReportID))
Otherwise, we can subscribe to the whole collection and use a selector to only select the transaction that is linked with the IOU report id. We will need to make a lot of changes to the code too, for example, we need to pass the transaction array to getTransactionsWithReceipts
instead of the iou report id (we can technically keep it as it is).
The receipts in the expense preview in workspace chat does not update. It only shows +1 counter after refreshing the page
We use transactionsWithReceipts to get the receipts here
Here's the detail implementation of getTransactionsWithReceipts, we get all transactions from onyx.connect
In theory, when the transactions get changed, transactions
from Onyx.connect will be updated first, and then re-render ReportPreview so when re-calculating getTransactionsWithReceipts
, we can get the updated transactions
But in ReportPreview
, we just subscribed ONYXKEYS.COLLECTION.TRANSACTION
in onyxSubscribe, so when transactions
get changed, we did not update transactionsWithReceipts
We should cover all places that use data from Onyx.connect
, we have iouSettled and transactionsWithReceipts
we're getting report
from allReports
but it works well for now, because we passed iouReport
to ReportPreview
so this component is re-rendered when iouReport
get changed
If we just create the new state for it and update in onyxSubscribe
, we will break the advantage of onyxSubscribe
, because transactionsWithReceipts
is the array (not just boolean as hasMissingSmartscanFields
, areAllRequestsBeingSmartScanned
,...) and updating to new array will make the component re-render every times
To address this issue, we should create the new state for transactionsWithReceipts
and just get the necessary properties from transactions
. We just update transactionsWithReceipts
in onyxSubscribe
when it's really changed
const [transactionsWithReceipts, setTransactionsWithReceipts] = useState([]);
const transactionsWithReceiptsRef = useRef([])
...
useEffect(() => {
const unsubscribeOnyxTransaction = onyxSubscribe({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (allTransactions) => {
const newTransactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(props.iouReportID).map(t=>({
receipt: t.receipt,
filename: t.filename,
pendingFields: t.pendingFields,
}))
if(!_.isEqual(newTransactionsWithReceipts, transactionsWithReceiptsRef.current)){ // deep comparison
setTransactionsWithReceipts(newTransactionsWithReceipts)
transactionsWithReceiptsRef.current = newTransactionsWithReceipts
}
...
NA
@cubuspl42, @Christinadobrzyn Whoops! This issue is 2 days overdue. Let's get this updated quick!
@bernhardoj @tienifr Would you consider adding to your GitHub profile...
@cubuspl42 sorry to hear that, I have added a profile picture
@tienifr
Can you use transactionsWithReceipts
inside onyxSubscribe
's callback
anonymous function just like that?
The surrounding useEffect
has empty deps ([]
), there's no useCallback
involved.
@cubuspl42 Ah, I mean using transactionsWithReceiptsRef, we can use the previous transactionsWithReceipts through ref in callback function. I updated my proposal to use ref as well
@tienifr
Now transactionsWithReceipts
seems to be unused...
const [transactionsWithReceipts, setTransactionsWithReceipts] = useState([]);
Have you tested these changes? While I agree that it might be beneficial to give some focus to the performance here, the code must first and foremost work.
Would you share the branch with the code you used for testing that the proposed solution works as expected?
@cubuspl42 Here you are: https://github.com/tienifr/App/tree/fix/33550
Evidence video:
https://github.com/Expensify/App/assets/113963320/c33662b8-96d0-4e52-85ad-1f015a17c6e8
Thank you!
I'm still in two minds with this one, though... While it's good that the proposal tries to focus on providing good performance, the exact solution feels somewhat hacky.
We're using both a ref and a state property to store transactionsWithReceipts
. I know that this pattern is sometimes used in the React world, but it feels at least workaroundish and I wouldn't apply it unless really necessary.
@bernhardoj
Do you know what's the performance characteristic of your solution? How often would setTransactionsWithReceipts
be called? Only once per specific user interaction (like adding/deleting a receipt), or tens/hundreds/thousands of times per second?
@cubuspl42 because we subscribe to the whole transaction, setTransactionsWithReceipts
will be called whenever the collection is updated. It can be updated by:
I guess we can do something like this to prevent the re-render if the array contents are the same.
setTransactionsWithReceipts((prevTransactions) => {
const newTransactions = ReportUtils.getTransactionsWithReceipts(props.iouReportID);
return _.isEqual(prevTransactions, newTransactions) ? prevTransactions : newTransactions;
})
Talking about the performance, the current usage of onyxSubscribe
is not really performant.
https://github.com/Expensify/App/blob/4a7014b980c6dc50cab671525ba017ceec5a8bdd/src/components/ReportActionItem/ReportPreview.js#L214-L226
Even though it doesn't cause a re-render, the four util functions make the same multiple loops to get all or linked transactions.
If we really want to improve the performance here, we can create like a wrapper for report preview like below. The 4 util functions above now only need to accept the transactions collection instead of the iou report ID.
function ReportPreviewWrapper(props) {
const linkedTransactions = useMemo(() => Object.values(props.transactions).filter((transaction) => `${transaction.reportID}` === `${props.iouReportID}`), [props.transactions, props.iouReportID]);
const transactionsWithReceipts = useMemo(() => linkedTransactions.filter((transaction) => TransactionUtils.hasReceipt(transaction)), [linkedTransactions]);
const hasMissingSmartscanFields = ...
const areAllRequestsBeingSmartScanned = ...
const hasOnlyDistanceRequests = ...
const hasNonReimbursableTransactions = ...
return <ReportPreview {...props} transactionsWithReceipts={transactionsWithReceipts} ... />
}
export default withOnyx({
transactions: {
key: ONYXKEYS.COLLECTION.TRANSACTION,
},
})(ReportPreviewWrapper);
And then memoized the ReportPreview.
memo(ReportPreview, (prevProps, nextProps) => _.isEqual(prevProps, nextProps))
Many things can be done to improve the performance, theoretically (because I didn't do any measurement).
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
@cubuspl42, @Christinadobrzyn Whoops! This issue is 2 days overdue. Let's get this updated quick!
@cubuspl42, @Christinadobrzyn Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
I decided that we should go with the proposal by @bernhardoj.
Thank you for raising the performance concerns, @tienifr. Unfortunately, the provided solution itself has issues, in my opinion, and I believe we should prioritize the simplicity here.
C+ reviewed π π π
Triggered auto assignment to @puneetlath, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
@cubuspl42 We still use ref and state in our App like useSingleExecution
, ... So I think it's the norm. I believe the performance issue is critical in this component because the transaction
collection is so big and the small change can cause the component re-render unexpectedly. As I mentioned above, we not only compare the previous transactionsWithReceipts
with the new one, but also restrict it with the necessary fields
@tienifr I hear you.
Still, this is not an issue related to the performance improvement project.
We do subscribe to the transaction collection inside each ReportPreview
and it's beyond the scope of this issue to change that.
Every change to the transaction collection will trigger transaction-processing computations inside each ReportPreview
, and it's beyond the scope of this issue to change that.
We do trigger React state setters inside the Onyx callback, so React will be forced to perform diffing in each ReportPreview
instance.
You're right, though, that after our changes, the triggered JS computations would be bigger, and the render would propagate.
I think that a relatively low-hanging fruit here is a trick...
We could split ReportPreview
into two components. An outer one which would exist for performance optimization reasons, and the inner one with the actual, more complex logic.
Inside the outer one, we could destructure the recent transactionsWithReceipts
.
const [recentTransaction1, recentTransaction2, recentTransaction3] = transactionsWithReceipts.slice(-3);
And we could cut the re-render based on the identity of these recent transactions. As we know, React prefers primitives and identity-stable objects when it comes to preventing re-renders.
We could experiment with this during the PR phase.
I could suggest a split compensation if the BugZero team agrees:
@puneetlath could you give your thoughts on this decision? https://github.com/Expensify/App/issues/33550#issuecomment-1873764619 Thank you!
I think we've done things like this in the past when a solution included a non-trivial part of a non-selected proposal. The issue budget stays the same. But if this is not fine by the process, we'll have to live with it.
@puneetlath @cubuspl42 @Christinadobrzyn this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
I'm going to be ooo next week so assigning a buddy to monitor this.
status- I think we're reviewing this - https://github.com/Expensify/App/issues/33550#issuecomment-1873764619
Current assignee @puneetlath is eligible for the Bug assigner, not assigning anyone new.
Platforms
in OP are β
)@puneetlath, @cubuspl42, @Christinadobrzyn Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
The proposal and the proposed compensation sounds good to me. Good by you @bernhardoj? If so, I'll assign you.
@puneetlath I'm good with that
@puneetlath @cubuspl42 @Christinadobrzyn this issue is now 3 weeks old. There is one more week left before this issue breaks WAQ and will need to go internal. What needs to happen to get a PR in review this week? Please create a thread in #expensify-open-source to discuss. Thanks!
π£ @cubuspl42 π An offer has been automatically sent to your Upwork account for the Reviewer role π Thanks for contributing to the Expensify app!
π£ @bernhardoj π An offer has been automatically sent to your Upwork account for the Contributor role π Thanks for contributing to the Expensify app!
Offer link Upwork job Please accept the offer and leave a comment on the Github issue letting us know when we can expect a PR to be ready for review π§βπ» Keep in mind: Code of Conduct | Contributing π
Assigning @bernhardoj. Agreed payment breakdown here: https://github.com/Expensify/App/issues/33550#issuecomment-1873818960
Actually, I wasn't able to reproduce this anymore because in this PR, we connect to the whole transactions collection π
Oh my, so this was one of those that you can wait out...
Ah, wow ok. So sounds like nothing to do here and we should just close the issue out?
Thanks for all the discussion though!
@puneetlath @cubuspl42 @bernhardoj @Christinadobrzyn this issue is now 4 weeks old and preventing us from maintaining WAQ, can you:
Thanks!
Current assignee @cubuspl42 is eligible for the Internal assigner, not assigning anyone new.
@cubuspl42 can you let us know what you think is fair for the partial payment?
Based on this Slack convo - https://expensify.slack.com/archives/C02NK2DQWUX/p1705917174041169
I paid out @cubuspl42 $125 (25% of $500) for the job. The payment is through Upwork.
Feel free to reach out with any questions!
If you havenβt already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
Version Number: 1.4.16-4 Reproducible in staging?: Y Reproducible in production?: Y If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by: Applause - Internal Team Slack conversation:
Action Performed:
Precondition: There are more than 3 requests in the workspace chat and 3 of them have a receipt
Expected Result:
The receipts in the expense preview in the workspace chat will update and +1 counter will show
Actual Result:
The receipts in the expense preview in workspace chat does not update. It only shows +1 counter after refreshing the page
Workaround:
Unknown
Platforms:
Which of our officially supported platforms is this issue occurring on?
Screenshots/Videos
Add any screenshot/video evidence
https://github.com/Expensify/App/assets/78819774/da6c2953-9e6a-4c52-8b94-7b09c3462651
View all open jobs on GitHub
Upwork Automation - Do Not Edit