score:6

Accepted answer

update (refactored code)

import { usenavigation, commonactions } from "@react-navigation/native";

export default function useresetprofilestacknavigator() {
  const navigation = usenavigation();

  return () => {
    const bottomtabnavigator = navigation
      .getstate()
      ?.routes?.find(({ name }) => name === "bottomtabnavigator");

    const profiletab = bottomtabnavigator?.state?.routes?.find(
      ({ name }) => name === "profilestacks"
    );

    const { key: target, routes } = profiletab?.state ?? {};

    if (!target || routes?.length <= 1) return;

    routes.length = 1; // poptotop()

    navigation.dispatch({
      ...commonactions.reset({ routes }),
      target,
    });
  };
}

and here is how to use it:

export default function usesendposttoprofile() {
  const navigation = usenavigation();

  const issending = useref(false);

  const resetprofilestacknavigator = useresetprofilestacknavigator();

  return (post) => {
    if (issending.current) return;

    issending.current = true;

    // make sure there is only one route open in the profile stack
    resetprofilestacknavigator();

    navigation.navigate("bottomtabnavigator", {
      screen: "profilestacks",
      params: {
        screen: "profile",
        params: {
          post,
        },
      },
    });
  };
}

previous solution

after a few hours studying the problem i have found a solution. it's not the best but it works for my use case and it surely works for other people's.

what i have tried to achieve is to reset the routes of the "profile" screen that is in a stack navigator that in turn is in another tab of the tab navigator in which my current stack screen is. it sounds somewhat confusing, but it is basically something similar to what happens on instagram when you upload a photo.

if in instagram you navigate to other users profiles from the home screen and then upload a photo to your account, you will see how you go from the "publish your photo" screen to the root of the stack navigator that is in the home tab, the feed.

in my use case, i am doing something similar, i can navigate to other users profiles from my own profile, and the photos are uploaded in this screen, with a progress bar.

from the beginning i had in mind to use navigation.poptotop (), but i have not been able to obtain the result i wanted, because as i have commented previously in the question, the parameters (that contained the post) were lost. so i have no choice but to simulate this behavior from my "publish photo" screen.

the steps i have followed are as follows:

  1. as my "publish photo" screen shares the navigation with my "profile" screen, through the tab navigator (which is obvious, since if it were not like that i could not do the navigation.navigate()), i have followed the navigation path from this to the stack navigator of the profile tab and then i have tried to take both its key and its routes.
  1. in case i have found the current key and paths, that means the stack navigator is mounted (in my case, the tab does a lazy initialization of all my pages, that's why i speak of "trying to take"). so it will be necessary to apply steps 3 and 4.
  1. simulate the navigation.poptotop() reducing the size of the routes to 1 (note that the root of the stack navigator is the item in the first position of the "routes" array)
  1. dispatch the reset operation over the profile's stack navigator using the navigation api.
  1. the final step, navigate to the stack screen normally passing the photo as param.

here is the code:

  const resetprofilestacknavigator = () => {
      const currentnavigationstate = navigation.dangerouslygetstate();

      // find the bottom navigator
      for (let i = 0; i < currentnavigationstate?.routes?.length; i++) {
        if (currentnavigationstate.routes[i].name === "bottomtabnavigator") {
          // get its state
          const bottomnavigationstate = currentnavigationstate.routes[i].state;

          // find the profile tab
          for (let j = 0; j < bottomnavigationstate?.routes?.length; j++) {
            if (bottomnavigationstate.routes[j].name === "profile") {
              // get its state
              const profiletabstate = bottomnavigationstate.routes[j].state;

              // get the key of the profile tab's stack navigator
              var targetkey = profiletabstate?.key;
              var targetcurrentroutes = profiletabstate?.routes;

              break;
            }
          }
          break;
        }
      }

      // reset the profile tab's stack navigator if it exists and has more than one stacked screen
      if (targetkey && targetcurrentroutes?.length > 1) {
        // set a new size for its current routes array, which is faster than array.splice to mutate
        targetcurrentroutes.length = 1; // this simulates the navigation.poptotop()

        navigation.dispatch({
          ...commonactions.reset({
            routes: targetcurrentroutes, // it is necessary to copy the existing root route, with the same key, to avoid the component unmounting
          }),
          target: targetkey,
        });
      }
 }


  /*
    maybe, the stack navigator of the profile tab exists and has changed from its initial state...
    in this situation, we will have to find the key of this stack navigator, which is also 
    nested in the same tab navigator in which this stack screen is.
  */
  resetprofilestacknavigator();

  // finally, navigate to the profile stack screen and pass the post as param
  navigation.navigate("profile", {
    screen: "profile",
    params: {
      post,
    },
  });

pd: i know there are some applicable refactorings, but i prefer to display the code this way so that the steps i discussed above are clearly visible.

if anyone who has read this manages to generalize this segment of code into a generic function using es6, please leave it as an answer, as it can be very useful for me and for other users.

score:1

this code is enough and it works

 <tab.screen name='searchnavigator' component={searchnavigator}
            listeners={props => tabpresslistener({ ...props })}/>

and this

const tabpresslistener = props => {
    const { navigation } = props
    return {
        blur: e => {
            const target = e.target
            const state = navigation.dangerouslygetstate()
            const route = state.routes.find(r => r.key === target)
            // if we are leaving a tab that has its own stack navigation, then clear it
            if (route.state?.type === "stack" && route.state.routes?.length > 1) {
                navigation.dispatch(stackactions.poptotop())
            }
        }
    }
}

score:1

as of react navigation 6.x you can easily achieve this with commonactions.reset. i wrote some example code based on the original question assuming only one stack, haven't tested for nested stacks but the solution might be something similar.

import {commonactions} from '@react-navigation/native';

navigation.dispatch((state) => {
            const params = state.routes[state.routes.length - 1].params;

            return commonactions.reset({
              index: 1,
              routes: [{name: 'home', params}]
            });
          });

score:4

i was struggling with similar issue. my case was that i want to have the same stack navigator and the tabs would be just different starting points, something like 2 home screens. it is the behavior that is seen in spotify for android, for example - we have home, search and library and all of them have common screens such as album screen and song screen. and when the user clicks on one of the tabs, the stack is cleared (like poptotop() should do).

my solution was to have bottom tab navigator with stack navigators that have the same screens - discovernavigator and searchnavigator:

const searchnavigator = () => (
    <stack.navigator headermode='screen'>
        <stack.screen name='search' component={searchscreen} />
        <stack.screen name='searchresults' component={searchresultsscreen} />
        <stack.screen name='item' component={itemscreen} />
    </stack.navigator>
)

const discovernavigator = () => (
    <stack.navigator headermode='screen'>
        <stack.screen name='discover' component={discoverscreen} />
        <stack.screen name='search' component={searchscreen} />
        <stack.screen name='searchresults' component={searchresultsscreen} />
        <stack.screen name='item' component={itemscreen} />
    </stack.navigator>
)

and the trick is to add a listener on blur for the tabs, like that:

<navigationcontainer>
    <tab.navigator>
        <tab.screen name='discovernavigator' component={discovernavigator}
            listeners={props => tabpresslistener({ ...props })}
        />
        <tab.screen name='searchnavigator' component={searchnavigator}
            listeners={props => tabpresslistener({ ...props })}
        />
    </tab.navigator>
</navigationcontainer>

that the handler for the blur event will check if the current tab has its own stack navigation and if it should clear it:

const tabpresslistener = props => {
    const { navigation } = props
    return {
        blur: e => {
            const target = e.target
            const state = navigation.dangerouslygetstate()
            const route = state.routes.find(r => r.key === target)
            // if we are leaving a tab that has its own stack navigation, then clear it
            if (route.state?.type === "stack" && route.state.routes?.length > 1) {
                navigation.dispatch(stackactions.poptotop())
            }
        }
    }
}

here is a demo: https://snack.expo.io/@monikamateeva/bottom-tab-navigation-with-poptotop

and this is all of the code:

import { createbottomtabnavigator } from '@react-navigation/bottom-tabs'
import { navigationcontainer, commonactions, stackactions } from '@react-navigation/native'
import { createstacknavigator } from '@react-navigation/stack'
import react from 'react'
import { button, stylesheet, view } from 'react-native'
import { enablescreens } from 'react-native-screens';
enablescreens();

const stack = createstacknavigator()
const tab = createbottomtabnavigator()

const itemscreen = ({ navigation, route }) => {
    react.uselayouteffect(() => {
        navigation.setoptions({
            title: `item ${route.params?.id}`,
        })
    }, [navigation, route])

    return (
        <view style={styles.container}>
            <button title='item 2' onpress={() => navigation.push('item', { id: 2 })} />
        </view>
    )
}

const searchresultsscreen = ({ navigation, route }) => (
    <view style={styles.container}>
        <button title={`item ${route.params?.id}`} onpress={() => navigation.push('item', { id: route.params?.id })} />
    </view>
)

const discoverscreen = ({ navigation }) => (
    <view style={styles.container}>
        <button title='search results 20' onpress={() => navigation.navigate('search', { screen: 'searchresults', params: { id: 20 } })} />
        <button title='item 20' onpress={() => navigation.navigate('search', { screen: 'item', params: { id: 20 } })} />
    </view>
)

const searchscreen = ({ navigation }) => (
    <view style={styles.container}>
        <button title='search results 10' onpress={() => navigation.push('searchresults', { id: 20 })} />
        <button title='item 10' onpress={() => navigation.push('item', { id: 10 })} />
    </view>
)

const searchnavigator = () => (
    <stack.navigator headermode='screen'>
        <stack.screen name='search' component={searchscreen} />
        <stack.screen name='searchresults' component={searchresultsscreen} />
        <stack.screen name='item' component={itemscreen} />
    </stack.navigator>
)

const discovernavigator = () => (
    <stack.navigator headermode='screen'>
        <stack.screen name='discover' component={discoverscreen} />
        <stack.screen name='search' component={searchscreen} />
        <stack.screen name='searchresults' component={searchresultsscreen} />
        <stack.screen name='item' component={itemscreen} />
    </stack.navigator>
)

const tabpresslistener = props => {
    const { navigation } = props
    return {
        blur: e => {
            const target = e.target
            const state = navigation.dangerouslygetstate()
            const route = state.routes.find(r => r.key === target)
            // if we are leaving a tab that has its own stack navigation, then clear it
            if (route.state?.type === "stack" && route.state.routes?.length > 1) {
                navigation.dispatch(stackactions.poptotop())
            }
        },
        // log the state for debug only
        state: e => {
            const state = navigation.dangerouslygetstate()
            console.log(`state`, state)
        }
    }
}

const appnavigator = () => {
    return (
        <navigationcontainer name="bottomtabnavigator">
            <tab.navigator>
                <tab.screen
                    name='discovernavigator'
                    component={discovernavigator}
                    listeners={props => tabpresslistener({ ...props })}
                />
                <tab.screen
                    name='searchnavigator'
                    component={searchnavigator}
                    listeners={props => tabpresslistener({ ...props })}
                />
            </tab.navigator>
        </navigationcontainer>
    )
}

export default appnavigator

const styles = stylesheet.create({
    container: {
        flex: 1,
    },
})


Related Query

More Query from same tag