Skip to content

[hold for payment 25 august] Onyx changes break Exisiting App architecture. #4500

Closed
Expensify/react-native-onyx
#96

Description

@parasharrajat

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


cc: @marcaaron @kidroca

ISSUE

There few parts that will stop working due to the recent changes to Onyx. We have to either plan for migration or fix Onyx so that those continue working.

There is a component Enablepayment which uses the following implementation.

  1. It disregards the stored values in Onyx for userWallet.
    export default withOnyx({
    userWallet: {
    key: ONYXKEYS.USER_WALLET,
    // We want to refresh the wallet each time the user attempts to activate the wallet so we won't use the
    // stored values here.
    initWithStoredValues: false,
    },
    })(EnablePaymentsPage);
    .
  2. We start with a userWallet:{} value.
  3. After the component is mounted we make an API call to get the new data.
  4. But If the data coming from the backend is the same as the stored value in Onyx, there will not be any rerendered.
  5. Due to this, we will never be able to show the real data on the screen.
  6. There will be many instances of this approach in the App and multiple times data will not change but we still want to make a request to check for updated value and show it to the user.

Slack thread => https://expensify.slack.com/archives/C01GTK53T8Q/p1628106963153400

Expected Result:

UI should be updated to show the recent state of data.

Actual Result:

  1. No change in the UI state.

Workaround:

None. It breaks the App.

Platform:

Where is this issue occurring?

  • Web
  • iOS
  • Android
  • Desktop App
  • Mobile Web

Version Number: 1.0.82
Logs: https://stackoverflow.com/c/expensify/questions/4856
Notes/Photos/Videos: Any additional supporting documentation
Expensify/Expensify Issue URL:

View all open jobs on Upwork

Activity

  1. MelvinBot commented on Aug 7, 2021

    @MelvinBot
    Contributor

    Triggered auto assignment to @arielgreen (AutoAssignerTriage), see https://stackoverflow.com/c/expensify/questions/4749 for more details.

  2. removed
    AutoAssignerTriageAuto assign issues for triage to an available triage team member
    on Aug 7, 2021
  3. parasharrajat commented on Aug 7, 2021

    @parasharrajat
    MemberAuthor

    My point here is What is an issue with triggering a render even if data is not changed? If user merge(Onyx.merge) or set(Onyx.set) a new data and we should trigger a render.

    If we want to block a render then this should be handled in the component level shouldComponentUpdate.

    The issue seems to be due to the Object comparison here. https://github.com/Expensify/react-native-onyx/blob/d7553b95e982ab78f6bb2064f6b0549f0ace94c2/lib/Onyx.js#L479

    Even if we want to drop a render if data is not changed and then we should only do Instance matching (ob1 === ob2).

  4. kidroca commented on Aug 9, 2021

    @kidroca
    Contributor

    We're not doing it to skip a render, but to skip re-writing the same data to storage
    You can add a log point in the code you've linked to see it's often the case we're trying to write the same value

    To let components re-render and still skip a write to storage we can move this to the top of the method

        // Optimistically inform subscribers on the next tick
        Promise.resolve().then(() => keyChanged(key, val));

    But do we want to raise a "change" event, when storage didn't really change ?


    It doesn't seem valid to use something like initWithStoredValue: false to capture loading state

    • what if you're offline - you'll be stuck loading and with a fullscreen loader it might be impossible to go back
    • if the network call fails - you're stuck loading as well

    Instead of relying on storage to raise a "change" event, even though it didn't really change, we can capture a loading state

    • either locally in the component state: { isLoading: true }
      • not possible due to current architecture that intentionally hides promises
    • or in the storage data itself - { walletKey1, walletKey2, isRefreshing: true }
      • this is already an existing pattern: Onyx.merge(ONYXKEYS.PLAID_BANK_ACCOUNTS, {loading: true});

    It's cleaner and easier to reason about instead of having to explain why the component uses initWithStoedValues

  5. kidroca commented on Aug 9, 2021

    @kidroca
    Contributor

    After analysing initWithStoedValues the component shared in the ticket description is the only component relying on initWithStoredValue for its loading state

    initWithStoredValue has 6 usages

    1. AdditionalDetailsStep (walletAdditionalDetails)

    Has defaultProps and tracks loading state

    walletAdditionalDetails: {
    errorFields: [],
    loading: false,
    additionalErrorMessage: '',
    },

    2. EnablePaymentsPage (userWallet)

    Discussed in the current issue - does not track loading state but relies on initWithStoedValues and storage to change

    3. OnfidoStep (walletOnfidoData)

    Has defaultProps and tracks loading state

    const defaultProps = {
    walletOnfidoData: {
    loading: false,
    hasAcceptedPrivacyPolicy: false,
    },
    };

    4. TermsStep (walletTerms)

    Has defaultProps and tracks loading state

    const defaultProps = {
    walletTerms: {
    loading: false,
    },
    };

    5. Expensify (updateAvailable)

    It's possible to have a problem here, updateAvailable is not read from storage, and the storage value could be true for some reason.

    App/src/Expensify.js

    Lines 53 to 63 in 50828ab

    const defaultProps = {
    session: {
    authToken: null,
    accountID: null,
    redirectToWorkspaceNewAfterSignIn: false,
    },
    updateAvailable: false,
    initialReportDataLoaded: false,
    isSidebarLoaded: false,
    betas: [],
    };

    I don't know why we ignore the storage value of updateAvailable, I guess there's a check that would set it to true at some point. If that's the case it can just be made to always init as false here:

    initialKeyStates: {

    6. LHN loading state

    let isSidebarLoaded;
    Onyx.connect({
    key: ONYXKEYS.IS_SIDEBAR_LOADED,
    callback: val => isSidebarLoaded = val,
    initWithStoredValues: false,
    });

    Always treated like false until it loads

    function setSidebarLoaded() {
    if (isSidebarLoaded) {
    return;
    }
    Onyx.set(ONYXKEYS.IS_SIDEBAR_LOADED, true);
    Firebase.stopTrace(CONST.TIMING.SIDEBAR_LOADED);
    }

    BTW it's already initalised with false and initWithStoedValues is not needed

    [ONYXKEYS.IS_SIDEBAR_LOADED]: false,

  6. parasharrajat commented on Aug 9, 2021

    @parasharrajat
    MemberAuthor

    Nice writeup. So what should be done for these cases?
    I think initWithStoredValues should be considered a special case. Instead of causing write, can't we just update the state and trigger data flow?

    It doesn't seem valid to use something like initWithStoredValue: false to capture loading state

    I don't tightly couple the usages of initWithStoredValue with capturing loading state. Although, it is used in this way, overall there is a case to consider.

    1. A component initialized with initWithStoredValue: false.
    2. After the API call, we get the same data.
    3. Component does not update??? <= what about this? shouldn't the data be passed down to the component when we call Onyx.set or merge.
  7. marcaaron commented on Aug 9, 2021

    @marcaaron
    Contributor

    Great conversation around this and it sounds like it might be worth exploring a deprecation of initWithStoredValues based on @kidroca's insights.

    Is there some interim solution we can go with for now? e.g. could we always notify any subscribers that have initWithStoredValues: false to get around this issue and quickly restore the previous behavior?

  8. parasharrajat commented on Aug 9, 2021

    @parasharrajat
    MemberAuthor

    @marcaaron yeah we can just send the notification without updating the Onyx Store.
    There are two ways to do this as per the code.

    1. we move the keychanged call to before checking the cache-store and returning early with an empty promise.
      https://github.com/Expensify/react-native-onyx/blob/d7553b95e982ab78f6bb2064f6b0549f0ace94c2/lib/Onyx.js#L479.

    2. we do the above but pass a parameter to keychanged to only trigger notifications for keys which has initWithStoredValues: false.

    It solves the issue without breaking new changes of Onyx.

  9. marcaaron commented on Aug 9, 2021

    @marcaaron
    Contributor

    I'm not sure 1 will work - bc we need to set the value before calling keyChanged()?

    we do the above but pass a parameter to keychanged to only trigger notifications for keys which has initWithStoredValues: false

    So something like this... ?

    const shouldCacheNewValue = !cache.hasCacheForKey(key) || !_.isEqual(val, cache.getValue(key));
    
    if (shouldCacheNewValue)
        cache.set(key, val);
    }
    
    // Optimistically inform subscribers on the next tick
    Promise.resolve().then(() => keyChanged(key, val, shouldCacheNewValue));

    Then handle the update to subscribers in keyChanged()?

  10. parasharrajat commented on Aug 9, 2021

    @parasharrajat
    MemberAuthor

    Oh Yeah. for the other cases, this is necessary. Sorry slipped from my mind.

    Then, if shouldCacheNewValue is false, only trigger a notification only for initWithStoredValues: false if we want to skip unnecessary render for all keys.

  11. removed their assignment
    on Aug 9, 2021
  12. 58 remaining items

  13. kidroca commented on Aug 18, 2021

    @kidroca
    Contributor

    I'll be happy to open a PR to address the regressions, as it's caused by a PR of mine
    We just need to agree on the solution
    We've discussed 1) before, and have agreed it could only improve things.

    Both approaches are pretty much handled the same way - we give "Cache" a list of keys that should not be cleared

  14. laurenreidexpensify commented on Aug 18, 2021

    @laurenreidexpensify
    Contributor

    Okay thanks for the summary - let's wait for Marc to get online later and take a look here to give us a forward steer 👍🏽

  15. parasharrajat commented on Aug 18, 2021

    @parasharrajat
    MemberAuthor

    Thanks, @kidroca, I agree with the point that changes done in the linked PR are not the cause of the issue. It's just the side case was not handled.

    Anyways, I am sure that we are going to keep the LRU cache so a fix to the occurred issue #4500 (comment) and #4509 and changes in Expensify/react-native-onyx#96, all are needed.

  16. parasharrajat commented on Aug 18, 2021

    @parasharrajat
    MemberAuthor

    @marcaaron Awaiting your response in the discussion. Please let us know what is the next thing to do.

  17. marcaaron commented on Aug 18, 2021

    @marcaaron
    Contributor

    Sorry, I will be tied up with some other duties for a bit. But I don't think there is anything that urgently needs to be done here. I understand @parasharrajat was assigned to this issue. The reverts have resolved the original issue. This should also no longer be a deploy blocking issue and can be closed.

    I'd suggest we create a new issue to discuss further improvements and just close this one.

  18. parasharrajat commented on Aug 25, 2021

    @parasharrajat
    MemberAuthor

    Any update on Upwork @laurenreidexpensify ?

  19. botify commented on Aug 30, 2021

    @botify

    🚀 Deployed to production by @roryabraham in version: 1.0.88-2 🚀

    platform result
    🤖 android 🤖 success ✅
    🖥 desktop 🖥 success ✅
    🍎 iOS 🍎 failure ❌
    🕸 web 🕸 success ✅
  20. parasharrajat commented on Sep 7, 2021

    @parasharrajat
    MemberAuthor

    Neither I recieved the job offer nor I got paid for reporting the issue and submitting the PR. Would someone like to clarify what's up with this issue?

    I am waiting for a clarification from a couple of days now. #4500 (comment)

  21. parasharrajat commented on Sep 8, 2021

    @parasharrajat
    MemberAuthor

    cc: @MitchExpensify Out of nowhere. Sorry.

  22. MitchExpensify commented on Sep 8, 2021

    @MitchExpensify
    Contributor

    Hey @parasharrajat ! Sorry for the delay, Lauren is out of office right now. I can jump in :)

    Hire offer sent, please accept and I will pay for job completion. To confirm, did you identify this issue? It appears so but just making sure

  23. parasharrajat commented on Sep 8, 2021

    @parasharrajat
    MemberAuthor

    @MitchExpensify
    I submitted PR which solved this issue but multiple issues were found with Onyx afterward. Thus a decision was taken to revert PR #4509 which caused all of those issues. And so my PR became unnecessary and was also reverted. But PR did solve this issue.

  24. parasharrajat commented on Sep 10, 2021

    @parasharrajat
    MemberAuthor

    Any update @MitchExpensify? Sorry for the confusing comment, I have updated it.

  25. MitchExpensify commented on Sep 10, 2021

    @MitchExpensify
    Contributor

    Paid with bonus! Thanks @parasharrajat

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

EngineeringExternalAdded to denote the issue can be worked on by a contributorHourlyKSv2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions