Expensify / App

Welcome to New Expensify: a complete re-imagination of financial collaboration, centered around chat. Help us build the next generation of Expensify by sharing feedback and contributing to the code.
https://new.expensify.com
MIT License
3.48k stars 2.83k forks source link

[HOLD] mWeb - Chrome start page opens tapping back button from chat window #7618

Closed kavimuru closed 2 years ago

kavimuru commented 2 years ago

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


Action Performed:

  1. Go to https://staging.new.expensify.com/[](javascript:) and sign in
  2. Tap the Search button and search any user to start a new chat
  3. Tap the Back button on the chat to return to Chats window
  4. Observe loaded page

Expected Result:

User returns to the Chats window, list of chats is displayed after tapping the Back button on the chat window

Actual Result:

Chrome start page loaded after tapping Back button on chat window. The issue occurs with a new chat. The issue doesn't occur with existing chats. The same issue with a new group chat.

Workaround:

Unknown

Platform:

Where is this issue occurring?

Version Number: 1.1.37-0 Reproducible in staging?: Y Reproducible in production?: Y Logs: https://stackoverflow.com/c/expensify/questions/4856 **Notes/

https://user-images.githubusercontent.com/43996225/152889875-f6ebc564-9377-4a1b-b8b3-c382d8ff3e2c.mp4

Photos/Videos:**

Upwork job link: https://www.upwork.com/jobs/~019f9c795e01626473 Issue reported by: Applause Bug5442931_Screenshot_20220207-232101

Slack conversation:

View all open jobs on GitHub

MelvinBot commented 2 years ago

Triggered auto assignment to @puneetlath (Engineering), see https://stackoverflow.com/c/expensify/questions/4319 for more details.

puneetlath commented 2 years ago

Is this only happening on mobile web and no other platform?

puneetlath commented 2 years ago

Bump on that question ^ @kavimuru.

Also, the end of that video is really fast. It's hard for me to tell what is going on.

kavimuru commented 2 years ago

@puneetlath When the tester taps back button it takes user to previous page of the chrome. It happens only with chrome browser not in safari. Web, apps takes user to chats list page.

https://user-images.githubusercontent.com/43996225/153616795-67547d3f-35f5-43b1-bdec-6485daad6ddf.mp4

.

puneetlath commented 2 years ago

Got it thanks!

MelvinBot commented 2 years ago

Triggered auto assignment to @michaelhaxhiu (External), see https://stackoverflow.com/c/expensify/questions/8582 for more details.

michaelhaxhiu commented 2 years ago

Upwork job link - https://www.upwork.com/jobs/~019f9c795e01626473

MelvinBot commented 2 years ago

Triggered auto assignment to Contributor-plus team member for initial proposal review - @parasharrajat (Exported)

MelvinBot commented 2 years ago

Triggered auto assignment to @puneetlath (Exported), see https://stackoverflow.com/c/expensify/questions/7972 for more details.

michaelhaxhiu commented 2 years ago

Job value moving up to $500 since 1 week and no takers.

MelvinBot commented 2 years ago

Current assignee @parasharrajat is eligible for the Exported assigner, not assigning anyone new.

MelvinBot commented 2 years ago

Triggered auto assignment to @iwiznia (Exported), see https://stackoverflow.com/c/expensify/questions/7972 for more details.

puneetlath commented 2 years ago

@iwiznia I'm about to head out on my two-month sabbatical, so handing the CME for this issue off to you. Thanks!

michaelhaxhiu commented 2 years ago

Doubling price to $1000.

michaelhaxhiu commented 2 years ago

Doubling price to $2000.

michaelhaxhiu commented 2 years ago

Doubling price to $4000.

michaelhaxhiu commented 2 years ago

@iwiznia do you agree with doubling this again?

iwiznia commented 2 years ago

I guess so

michaelhaxhiu commented 2 years ago

Doubling price to $8000

michaelhaxhiu commented 2 years ago

Doubling price to $16000

quinthar commented 2 years ago

Doubling again to $32,000 big ones!

mdneyazahmad commented 2 years ago

Adding more context to this issue. This may be related.

When you open a tab and visit any url in which the RHN is opened and click on the backdrop it gets it back to browser home page.

Steps to reproduce:

  1. Open a new tab.
  2. Enter url on address bar eg. https://new.expensify.com/settings
  3. Click on backdrop.

https://user-images.githubusercontent.com/77761491/163802534-5605054f-3f6d-4326-9eed-8455b60e219f.mp4

parasharrajat commented 2 years ago

Thank you @mdneyazahmad

aneequeahmad commented 2 years ago

PROPOSAL

Solution

What is fixed

So by applying this fix all the issues happing on Mweb, or normal web are fixed.

The code change is in the SearchPage.js in selectReport function. The code change is

   if (option.reportID) {
          Navigation.dismissModal()  // new line to dismiss modal
            this.setState({
                searchValue: '',
            }, () => {
            // setTimeout is applied on navigation.navigate
              setTimeout(() => {    
                Navigation.navigate(ROUTES.getReportRoute(option.reportID));
              }, 10)
            });
        } else {
            Report.fetchOrCreateChatReport([
                this.props.session.email,
                option.login,
            ]);
        }

I'm not sure about the setTimeout thing without it fix is not working. I guess SetTimeout is needed so that the actions or api calls in background are executed before this Navigation.navigate(ROUTES.getReportRoute(option.reportID)); is executed.

After the fix is applied (Note in last i'm pressing browser back button which is navigating fine.

https://user-images.githubusercontent.com/36359331/164053466-8162dedb-c746-4db8-a316-e2cd0a348055.mp4

Web after the fix is applied.

https://user-images.githubusercontent.com/36359331/164054863-550f91b8-f251-407a-a95e-79a1efe2d3e6.mp4

cc: @michaelhaxhiu

parasharrajat commented 2 years ago

Thanks for the proposal @aneequeahmad.

So when search page is opened and we select report from search page we dont dismiss the modal so if we dismiss the modal when report is selected it fixes the issues also we have to apply setTimeout on this line of code

I do not agree with this statement. We close the modal from the navigation.navigate function. How do you think the modal is closed on clicking on any report from the search Page?

Could you please use bullet points to explain all the issues you are talking about?

You are vouching for the use of SetTimeout but settimeout are never considered as a solution. But if you think this is the only way, Could you please raise this in Slack and get others' opinions?

aneequeahmad commented 2 years ago

@parasharrajat

I dont see any Navigation.dismissModal() in navigation.navigateas the following code is executed on select report from search page

if (isDrawerRoute(route)) {
        navigationRef.current.dispatch(CustomActions.pushDrawerRoute(route));
        return;
    }

I've updated the comment above with bullet points.

Yes, but without setTimeout things are not working. This is the only way I've debugged thoroughly but only setTimeout is fixing the issue as i mentioned the cause in my opinion. Raising it in Slack for others opinions.

aswin-s commented 2 years ago

Reason

There are three scenarios where this issue seems to occur.

  1. Sign-in on mobile web, navigate to a report and then tap back button on report header
  2. Start group chat with a new user by tapping ‘New’ button. Navigate to the chat and then press back arrow on report header.
  3. Navigate directly to a nested path while signed in, like https://staging.new.expensify.com/settings and tap outside

The issue seems to stem from a bug inside the react-navigation library itself. It is related to how deep links get handled in react-navigation and there are a couple of open issues in their repo which already discuss this bug. To verify that the bug is indeed caused due to this, just remove linking prop from below code block and test the scenarios above.

https://github.com/Expensify/App/blob/b93659cc6eaecaddf02622a1773d6b067c1ea761/src/libs/Navigation/NavigationRoot.js#L59-L76

Both scenarios 1 & 2 should pass. We cannot replicate 3, as removing linking config also removes nested URL navigation.

This points to a bug within the linking implementation of react-navigation library. Whenever we navigate directly to a nested route and try to go back to a parent path (Home in our case), it simply exits the app.

Diving deeper I traced the issue back to useLinking.tsx

When we navigate to a new path inside the app, onStateChange listener inside useLinking gets invoked. it tries to keep the app's internal state in sync with browser history. The listener compares current state with previous state to figure out whether user moved forward or backward in history. If user is going to a new path (historyDelta>0), it pushes a new path to history.

https://github.com/react-navigation/react-navigation/blob/b1c421445ecb75d514ce27791433b12f9a7e75b7/packages/native/src/useLinking.tsx#L577-L580

If user goes to a previous path then historyDelta becomes negative. Then it tries to find the backIndex from its available local history and navigates to the previous path index if the path is found in memory. If path is not found in local state it tries to navigate back in history byhistoryDelta steps.

https://github.com/react-navigation/react-navigation/blob/b1c421445ecb75d514ce27791433b12f9a7e75b7/packages/native/src/useLinking.tsx#L581-L596

Everything sounds reasonable until this point, except that when directly navigating to a nested URL and then going back, there is no previous history for current app in browser and it would simply exit the app. The code seems to be missing some logic to guard against this scenario.

Going inside history.go implementation we could see this line which notes that We shouldn't go back more than the 0 index (otherwise we'll exit the page). There also seems to be a condition which tries to guard this state

index = n < 0 ? Math.max(index - n, 0) : Math.min(index + n, items.length - 1); 

Math.max(index-n,0) works as long as index is greater than n and n is positive. Once n becomes negative index - (negativeN) becomes positive and the index gets assigned to a value higher than intended. This seems to be a bug. Quick thought was to add a condition which should prevent such a scenario.

if(index+n<0){
   return
}

That’s it!. Now everything works as expected. To verify it locally modify the file

node_modules/@react-navigation/native/lib/module/useLinking.js

Modify line 131 as shown below and test all the three scenarios mentioned above. It should all pass.

if (n === 0 || index+n<0) {
  return;
}

Proposal

Since this bug seems to originate outside of Expensify need raise a PR with the above fix over at @react-navigation and wait for it to get merged. Meanwhile we could create a patch package and add it to post install step. This works both for local development and CI environments.

https://user-images.githubusercontent.com/6880914/164079545-cb8efbc2-aec5-4e9b-996c-fdb62ef75896.mp4

izhan commented 2 years ago

Lurking here just out of curiosity (I clicked a link on a marketing email from Expensify that linked to this issue) - @aswin-s 's thorough explanation was really cool to read. If his proposal looks good, an alternative upstream fix could also be the more elegant:

index = Math.min(Math.max(index + n, 0), items.length - 1);
mdneyazahmad commented 2 years ago

I think it should also work with browser and android back button.

VishalMehta84 commented 2 years ago

@aswin-s very well explained and awesome work here

parasharrajat commented 2 years ago

Thanks for the proposal @aswin-s. Reviewing it ~shortly~ in a few hours.

parasharrajat commented 2 years ago

I was wondering if https://github.com/Expensify/App/issues/4612 might be fixed as well with the proposed solution. I will test it later.


Glad to see a detailed post.

Math.max(index-n,0) works as long as index is greater than n and n is positive. Once n becomes negative index - (negativeN) becomes positive and the index gets assigned to a value higher than intended. This seems to be a bug. Quick thought was to add a condition which should prevent such a scenario.

I am trying to understand this. It seems wrong to me. I hope you can help me understand it better.

index = n < 0 ? Math.max(index - n, 0) : Math.min(index + n, items.length - 1); based on this line Math.max(index-n,0) only runs when n < 0(negative) so this works as long as index is greater than n and n is positive. statement does not seem correct. When N is positive, it will never run.

Maybe you can provide more samples to verify this. I think it would be better to see what is the index value when this issue occurs.

I will test your proposal. If it is fixing the issue then great but we need to find what is really the root cause of the issue. Index as you say or something else.

aswin-s commented 2 years ago

@parasharrajat Let me elaborate it a bit more.

n : no of steps to go forward or backward in history
items : An array of history records in the format [{ id, state, path } ].
        It keeps track of paths the user has navigated until this point, corresponding state-Id in window.history.state and the 
        navigational state for that path
index : current index in local history.items 

While the issue occurs, state of things are as follows

n = -1
index = 0
items=[
    {
        "path": "/settings",
        "state": {
            "stale": false,
            "type": "stack",
            "key": "stack-v4lUoHF8I9ZEZtEvxa20A",
            "index": 1,
            "routeNames": [
                "Home",
                "ValidateLogin",
            ..
            ],
            "routes": [
                {
                    "name": "Home",
                    "key": "Home-GbYcmWDMwtZrWtQ66YUkQ",
                    "state": {
                        …
                    }
                },
                {
                    "name": "Settings",
                    "state": {
                       ..
                    },
                    "key": "Settings-BWJWNNnPrxgG846fZkOxK"
                }
            ]
        },
        "id": "ZrPdc2T6l7gk5kg0aXwvs"
    }
]

So when the resultant index gets calculated by this logic

index = n < 0 ? Math.max(index - n, 0) : Math.min(index + n, items.length - 1);
Math.max(0-(-1),0)

index becomes 1. But items.length is just 1. So index could never be greater than zero.

And finally navigation happens on line 196 using the value of n itself. So just fixing the index value might not be enough like @izhan suggested. Please correct me if I'm wrong.

window.history.go(n);
parasharrajat commented 2 years ago

OK, got it. Now the question is what is an acceptable solution to this bug. Let me do some testing on my end.

So just fixing the index value might not be enough.

Why?

aswin-s commented 2 years ago

Why

Finally redirection happens on like 196 using value of n, which is -1. index is calculated to map history to proper internal state. So even if we fix index and set it to zero, browser will still navigate to previous path which is outside the app.

mdneyazahmad commented 2 years ago

I have got the root cause of the this issue and one more earlier posted by me https://github.com/Expensify/App/issues/7618#issuecomment-1101334527 But, I do not have a solid proposal to fix this issue yet. Posting soon. Thank you!

parasharrajat commented 2 years ago

@aswin-s gotcha.

aswin-s commented 2 years ago

Figured out a way to fix this issue within Expensify codebase itself. It's basically just reversing the logic thats happening inside linking library.

When we navigate to ROUTES.HOME, DrawerActions.openDrawer() gets called which removes the last drawer state from history. So when onStateChange listener in useLinking gets fired, it sees that there is a route removed from history and it needs to go one step back in browser history and calls history.go(-1)

To avoids this right before opening the drawer, push a drawer state with status as 'open' right before root path. This would keep the history length intact even after openDrawer action and browser will never go back in history.

To achieve this modify navigate method in Navigation.js https://github.com/Expensify/App/blob/0d97ece7fe548eb00468245674f341850879b06c/src/libs/Navigation/Navigation.js#L122-L131

like this

function navigate(route = ROUTES.HOME) {
    if (!canNavigate('navigate', {route})) {
        return;
    }

    if (route === ROUTES.HOME) {
        if (isLoggedIn) {
            const state = navigationRef.current.getState();

            const newState = {
                ...state,
                routes: _.map(state.routes, (r) => {
                    if (r.state.type === 'drawer' && r.state.history.length === 2) {
                        const [firstRoute, lastRoute] = r.state.history;
                        const history = [firstRoute,
                            // Insert a history record to indicate drawer is open
                            {type: 'drawer', status: 'open'},
                            lastRoute];
                        return {...r, state: {...r.state, history}};
                    }
                    return r;
                }),
            };
            navigationRef.current.dispatch(CommonActions.reset(newState));
            // useLinking library will recognise the state changes only in the next tick, so we need a timeout
            setTimeout(openDrawer, 0);
            return;
        }

This solves scenarios 1 & 2 mentioned above. We still need another fix to handle navigating to deep linked URLs like /settings. The most easiest way to fix this is to push root path to history whenever previous state in browser history is not set by linking library. This could be identified by the presence of window.history.state.id.

Linking.getInitialURL()
 .then((url) => {
    if (Platform.OS === 'web' && !window.history.state) {
         window.history.pushState({
             id: SCREENS.HOME,
         }, null, '/');
     }
 });

This change fixes scenario 3 .

marktoman commented 2 years ago

Proposal

Fix the underlying issue, so the inner state of react navigation and browser history are in sync automatically.

In useLinking.js, change const historyDelta = ... to let historyDelta = ....

Below it, add:

// If we go back on one stack and, at the same time, forward on another stack, only the first action gets processed.
// Guard against this by using a middle version of the state, i.e. state adjusted by the first action.
// Then, compare it to the the final state.
if (historyDelta < 0 && previousFocusedState.index + historyDelta >= 0) {
  const middleIndex = previousFocusedState.index + historyDelta;
  const previousdRoutes = previousFocusedState.history || previousFocusedState.routes || [];

  if (middleIndex < previousdRoutes.length) {
    const middleState = {
      ...previousFocusedState,
      index: middleIndex,
      history: undefined,
      routes: previousdRoutes.slice(0, middleIndex + 1),
    }
    const [middleFocusedState, nextFocusedState] = findMatchingState(middleState, state);

    if (middleFocusedState && nextFocusedState) {
      historyDelta +=
        (nextFocusedState.history ? nextFocusedState.history.length : nextFocusedState.routes.length) -
        (middleFocusedState.history ? middleFocusedState.history.length : middleFocusedState.routes.length);
    }
  }
}

In CustomActions.js, change the return statement of pushDrawerRoute to:

const drawerKey = navigationRef.current.getRootState().routes[0].state.key; // TODO: Replace by a constnat

return CommonActions.reset({
    ...state,
    routes: [{
        key: newScreenName,
        name: newScreenName,
        params: newScreenParams,
    }],
    history,
    routeNames: [newScreenName],
    default: 'open',
    key: drawerKey,
    index: 0,
    stale: false,
    type: 'drawer',
});

It solves the 2 issues mentioned above. Dismissing the settings dialog when opening the URL is unrelated. Also, it would probably be better to dismiss it without depending on fake history steps. At least the effort indicated in the codebase comments is to preserve real history to the user.

I'll extend the description in the upcoming days.

izhan commented 2 years ago

Proposal

Problem

We are performing two navigations here:

  1. Navigating UP to the root drawer https://github.com/Expensify/App/blob/32d8f0261b825cb6e47e441b9cf08400340a164e/src/libs/Navigation/CustomActions.js#L79
  2. Navigating DOWN to the new chat https://github.com/Expensify/App/blob/32d8f0261b825cb6e47e441b9cf08400340a164e/src/libs/Navigation/CustomActions.js#L107

Unfortunately react-navigation has some...quirks...especially when you are navigating both UP and DOWN in one React render cycle.

What happens when a normal navigation occurs:

  1. A setState occurs on the state, with the newest state object in BaseNavigationContainer https://github.com/react-navigation/react-navigation/blob/main/packages/core/src/BaseNavigationContainer.tsx#L98
  2. After React renders are complete, the useEffect runs, which emits a "state" event with the latest updated state https://github.com/react-navigation/react-navigation/blob/a937523d59301013fabe872516da61f0cd5b304c/packages/core/src/BaseNavigationContainer.tsx#L370
  3. useLinking listens to the state event here https://github.com/react-navigation/react-navigation/blob/main/packages/native/src/useLinking.tsx#L617 and runs onStateChange
  4. onStateChange finds the difference between the two states https://github.com/react-navigation/react-navigation/blob/a937523d59301013fabe872516da61f0cd5b304c/packages/native/src/useLinking.tsx#L557 via findMatchingState and uses that to calculate what history to change, i.e. the historyDelta. Note that findMatchingState only compares ONE level deep.

When you do a normal navigation, everything works as intended. There is typically only one routes or history that has changed, and step 4 is able to compare the two states and figure out the difference. Note that the implementation in step 4 only does one history change, i.e. a single push / replace, etc

What's happening here The issue here is that when we dispatch two navigations at once, we end up with state objects with not one but TWO changes. Here's what the state object looked like before

Screen Shot 2022-04-21 at 8 34 40 PM

and after Image 4-21-2022 at 8 36 PM

Why is onStateChange running not with two updates, but one single merged update? It's because we dispatched these two navigations synchronously. Despite both states being updated in step 1 (the setState), step 2 (the useEffect hook) was only run ONCE for both of the changes because useEffect asynchronous and tied to the React render cycle.

Then as the merged states marches on to step 4, since onStateChange only shallowly compares differences, it only knows to call history.go(-1) and doesn't realize we need to push the drawer close event onto the history stack as well. Thus, when we try to navigate back, we're missing that extra entry, and instead of undoing the drawer close event, we instead navigate back to the Chrome homepage (or whatever website you were on before this)

Solution 1

We can change node_modules/@react-navigation/native/lib/module/useLinking.js to recursively look for changes in the route changes. Currently, onStateChange only looks at one layer of history changes but we can recursively let it call itself on children to continue looking for more changes.

Why recursively? While this bug is only one level deep (Home > Search, Home > Route), one can imagine having multiple nested navigations to handle.

We'll want to change onStateChange in https://github.com/react-navigation/react-navigation/blob/a937523d59301013fabe872516da61f0cd5b304c/packages/native/src/useLinking.tsx#L531 with

const onStateChange = async () => {
  const navigation = ref.current;

  if (!navigation || !enabled) {
    return;
  }

  const previousState = previousStateRef.current;
  const state = navigation.getRootState();

  // root state may not available, for example when root navigators switch inside the container
  if (!state) {
    return;
  }

  const pendingPath = pendingPopStatePathRef.current;
  const route = findFocusedRoute(state);
  const path = getPathForRoute(route, state);

  previousStateRef.current = state;
  pendingPopStatePathRef.current = undefined;

  // To detect the kind of state change, we need to:
  // - Find the common focused navigation state in previous and current state
  // - If only the route keys changed, compare history/routes.length to check if we go back/forward/replace
  // - If no common focused navigation state found, it's a replace
  let [previousFocusedState, focusedState] = findMatchingState(
    previousState,
    state
  );

  while (previousFocusedState && focusedState) {
    if (
      // We should only handle push/pop if path changed from what was in last `popstate`
      // Otherwise it's likely a change triggered by `popstate`
      path !== pendingPath
    ) {
      const historyDelta =
        (focusedState.history
          ? focusedState.history.length
          : focusedState.routes.length) -
        (previousFocusedState.history
          ? previousFocusedState.history.length
          : previousFocusedState.routes.length);

      if (historyDelta > 0) {
        // If history length is increased, we should pushState
        // Note that path might not actually change here, for example, drawer open should pushState
        history.push({ path, state });
      } else if (historyDelta < 0) {
        // If history length is decreased, i.e. entries were removed, we want to go back

        const nextIndex = history.backIndex({ path });
        const currentIndex = history.index;

        try {
          if (nextIndex !== -1 && nextIndex < currentIndex) {
            // An existing entry for this path exists and it's less than current index, go back to that
            await history.go(nextIndex - currentIndex);
          } else {
            // We couldn't find an existing entry to go back to, so we'll go back by the delta
            // This won't be correct if multiple routes were pushed in one go before
            // Usually this shouldn't happen and this is a fallback for that
            await history.go(historyDelta);
          }

          // Store the updated state as well as fix the path if incorrect
          history.replace({ path, state });
        } catch (e) {
          // The navigation was interrupted
        }
      } else {
        // If history length is unchanged, we want to replaceState
        history.replace({ path, state });
      }
    } else {
      // If no common navigation state was found, assume it's a replace
      // This would happen if the user did a reset/conditionally changed navigators
      history.replace({ path, state });
    }

    const prevChildState = previousFocusedState.routes[focusedState.index].state;
    const childState = focusedState.routes[focusedState.index].state;
    [previousFocusedState, focusedState] = findMatchingState(
      prevChildState,
      childState
    );
  }
};

It looks like a lot of code, but all I'm really doing is just refactoring / indenting code. The main change is the while loop and the extra findMatchingState call that continues searching for changes. If that solution looks ok, we'll need to update it in the react-navigation repo and do whatever patches needed on this end.

Solution 2

Solution 1 will work, but as noted in https://github.com/react-navigation/react-navigation/blob/main/packages/native/src/useLinking.tsx#L614 - the maintainers may not want us to be updating more than once. To solve this correctly, react-navigation may require a ton of rewrites, especially step 2's useEffect only-running-once issue. I looked into refactoring it, but I didn't see any quick wins 😄

So although I wouldn't recommend it, we can add a setTimeout to break up the navigateBackToRootDrawer() and CommonActions.reset(...) updates into two render cycles. That way, the useEffect runs once for each update. Again I wouldn't recommend it, but it would be a fix if there is no movement in the react-navigation repo. If you want me to outline what that would look like, I can – it's somewhat trivial.

izhan commented 2 years ago

ha @marktoman great minds think alike! looks like you beat me to posting a solution as I was still writing it up

+1 to @marktoman's comment that the modal issue should be treated separately

mujtabasac commented 2 years ago

According to what the developer comments suggest for this line of code it should be taking care of any value which is 0 or lesser than 0 where as it just matches to the value 0 only. Which is probably turning out to be buggy in our case. I've highlighted the from and to code for quick reference.

node_modules/@react-navigation/native/lib/module/useLinking.js - Line 131

Screen Shot 2022-04-22 at 8 48 13 AM

And this is the expected behavior in the attached video link. https://user-images.githubusercontent.com/72865619/164605632-d60a5320-6d13-42c8-9a61-649b35174d6e.mov

aneequeahmad commented 2 years ago

Ahhhh finally using setTimeout makes sense to someone hehe. @izhan look at my proposal here i suggested it already. it fixes all 3 solutions but before setTimeout, we have to dismiss the modal too and yeah i was curious if n<0 was not handeled in react-navigation how does my solution was working.

marktoman commented 2 years ago

@izhan Ha, and it took me forever to hit send because of re-testing. Nice write-up, you explain it really well.

marktoman commented 2 years ago

One way to treat the changes to react navigation is to keep them in the Expensify codebase by intent.

The pros:

Generally, the cons are that the upstream repo changes as well. In the case of useLinking, however, the code was written in a single bout of effort some two years ago and has not been touched ever since. In either case, this update is an insertion rather than a throughout modification.

Another thing is the compat package that Expenisfy uses for linking config. If they do a major rewrite of useLinking, it is very unlikely that they bring the compat features alongside it. I expect that adapting this update to a potential new version would be a lot less effort then replacing the linking config system.

The mature patching tooling makes this approach a viable option in my opinion.

parasharrajat commented 2 years ago

Thanks for the investigation, everyone. I am following each post and I will share the reviews asap.

gustavo-3 commented 2 years ago

Proposal

in App/src/libs/Navigation/CustomActions.js :

  import navigationRef from './navigationRef';
+ import getPlatform from '../getPlatform';
+ import CONST from '../../CONST';

...

- function navigateBackToRootDrawer() {
+ function navigateBackToRootDrawer(newScreenParams) {
    const activeState = getActiveState();

    // To navigate to the main drawer Route, pop to the first route on the Root Stack Navigator as the main drawer is always the first route that is activated.
    // It will pop all fullscreen and RHN modals that are over the main drawer.
    // It won't work when the main drawer is not the first route of the Root Stack Navigator which is not the case ATM.
    navigationRef.current.dispatch({
        ...StackActions.popToTop(),
        target: activeState.key,
    });

+    const routeName = navigationRef.current.getState().routes[1].name;
+    if (getPlatform() === CONST.PLATFORM.WEB && ['Search', 'NewGroup', 'NewChat', 'IOU_Request', 'IOU_Bill', +'IOU_Send'].includes(routeName)) {
+        window.history.pushState({}, '', `r/${newScreenParams.reportID}`);
+    }
}

...

        if (currentState.type !== 'drawer') {
-            navigateBackToRootDrawer();
+            navigateBackToRootDrawer(newScreenParams);

Result

https://user-images.githubusercontent.com/104327913/164999540-228655d4-1908-4385-aa82-b7dad826e560.mp4

tomivs commented 2 years ago

@gustavo-3 This one looks hacky. I don't think it is a good idea to be creating an if condition for each page.

gustavo-3 commented 2 years ago

@tomivs

  1. you can remove this if and the code still works fine but will face another issue when you try go back from Settings -->
    About --> Report a bug

  2. Could you please stop evaluating proposals? there is someone already reviewing these.

parasharrajat commented 2 years ago

Great proposals overall. At least we all agree that this is the issue with react-navigation.

I am reviewing each proposal one by one now. There are a few quick eliminations for proposals. @mdneyazahmad No solution yet. @aneequeahmad I think root cause analysis is wrong. No preferable solution. No points backing the solution. I'm not sure about the setTimeout thing without it fix is not working. @mujtabasac Feels duplicate. @gustavo-3 As others suggested the problem is with react-navigation. No proper explanation of the solution. Direct manipulation of history API even though we use react-navigation to do that for us.

Thank you for your proposals. Let us know if you have better solutions.


we would prefer if this is fixed upstream. Although, I agree that patching would be best to fast forward the fix on the App.

I would appreciate it if we keep the discussion positive, and ethical, and give constructive criticism.