Skip to content
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

[Feature Request] Can stack router have replace like History API? #802

Closed
joonhocho opened this issue Mar 24, 2017 · 33 comments
Closed

[Feature Request] Can stack router have replace like History API? #802

joonhocho opened this issue Mar 24, 2017 · 33 comments

Comments

@joonhocho
Copy link

joonhocho commented Mar 24, 2017

I think replace is very common routing pattern that html history api also has it.

You don't always want to push to stack, but sometimes want to replace it with or without animation instead of going through pop and push (two animation phases).

According to doc, calling reset will empty your stack, but that's not how replace should work.

It should work like so:
state: a -> b -> c
action: c calls replace(d)
result: a -> b -> d

state: a -> b -> c
action: b calls replace(d)
result: a -> d

@joonhocho
Copy link
Author

Related #295 #774

@joonhocho
Copy link
Author

Issues with using reset to replace:
#395 #691
#199
#335

@dioxide
Copy link

dioxide commented Mar 24, 2017

+1 , agree with you, the REPLACE pattern is very useful!

@justbettereveryday
Copy link

I just think that : it will empty the stack,but all objects you have registered before are also here,so you can use them as usual.

@joonhocho
Copy link
Author

@justbettereveryday Could you explain more about how to get all objects you have registered?

@maximovicd
Copy link

I also use replace in my app and I think it's a good idea to have an action like that.

Until that gets done, I have used a workaround of passing my own flag into a navigate action, and when I detect it, I manually kick the last route from state before doing getStateForAction. It seems to work, with a little problem of missing the animation.

@justbettereveryday
Copy link

justbettereveryday commented Mar 29, 2017

@joonhocho
Just like this:

A.js

  const DefaultLaunch = StackNavigator({

    Main: { screen: Main },

    Login: { screen: LoginComponent },

    MySetting: { screen: MySetting }},
   {
        headerMode: 'screen'
    })

Main.js

const MainScreen = TabNavigator({
    Task: { screen: Task },
    Shop: { screen: Shop }
})

then you can reset your current vc anytime!

@aranda-adapptor aranda-adapptor mentioned this issue Apr 6, 2017
9 tasks
@ericvicenti
Copy link
Contributor

I think it is a good idea to have replace functionality.

What if we added a key to the navigate action, so that you could specify the current key to replace the current screen?

Lets track this issue as a part of #135, where we plan on adding a key to the navigate action

@spencercarli
Copy link
Member

spencercarli commented Apr 24, 2017

For anyone else that comes along this issue I'm currently using the following solution. I specify a new action type inside the getStateForAction handler and then use dispatch rather than navigate in my component.

const prevGetStateForActionHomeStack = HomeStack.router.getStateForAction;
HomeStack.router = {
  ...HomeStack.router,
  getStateForAction(action, state) {
    if (state && action.type === 'ReplaceCurrentScreen') {
      const routes = state.routes.slice(0, state.routes.length - 1);
      routes.push(action);
      return {
        ...state,
        routes,
        index: routes.length - 1,
      };
    }
    return prevGetStateForActionHomeStack(action, state);
  },
};
replaceScreen = () => {
  const { locations, position } = this.props.navigation.state.params;
  this.props.navigation.dispatch({
    key: 'NearMeMap',
    type: 'ReplaceCurrentScreen',
    routeName: 'NearMeMap',
    params: { locations, position },
  });
};

I also put together a short video walking through this code, if that would help anyone.

@joonhocho
Copy link
Author

@spencercarli Awesome! Do you know how to animate replace action by any chance?

@spencercarli
Copy link
Member

@joonhocho I don't - do you have an example of what kind of animation you're thinking of?

@wilomgfx
Copy link

@spencercarli probably the base animation it has when navigating to a screen :p

@timscott
Copy link

timscott commented Jun 3, 2017

@spencercarli - I tried your solution, and it's not woking for me.

I check MyNavigator.router.getStateForAction just before I render it (<MyNavigator />), and it points to my custom function. However, when I reach the point to replace a route and call this.props.navigation.dispatch I can see that it's calling the original getStateForAction.

Thoughts?

@cyberdelphos
Copy link

@timscott I had to change the code a bit and it's working correctly.
Instead of:

HomeStack.router = {
  ...HomeStack.router,
  getStateForAction(action, state) {
     ...

Modified it directly:

HomeStack.router.getStateForAction = (action, state) => {
  ... (everything else remains the same)

You can see this direct modification mentioned in the docs for routers: https://reactnavigation.org/docs/routers/

@bleonard
Copy link

In case it's helpful, I tried a different approach to a new action name to try and not mess around as much as possible with the normal implementation of getStateForAction.

When I navigated, I did this:

navigation.navigate("ScreenName", { replaceRoute: true });

And then looked for that in the getStateForAction. By messing with the routes the normal one returned, I felt better about being more resistant to various interesting things the built-in stack router is doing.

/* @flow */

import type {
  NavigationAction,
  NavigationState
} from "react-navigation/lib/TypeDefinition";

import NavigationActions from "react-navigation/lib/NavigationActions";

const updateStackRouter = function(StackNavigator: any) {
  // generally defer to the "real" one
  const parentGetStateForAction = StackNavigator.router.getStateForAction;
  StackNavigator.router.getStateForAction = function(
    action: NavigationAction,
    inputState?: ?NavigationState
  ): ?NavigationState {
    const state: ?NavigationState = parentGetStateForAction(action, inputState);

    // fix it up if applicable
    if (state && action.type === NavigationActions.NAVIGATE) {
      if (action.params && action.params.replaceRoute) {
        delete action.params.replaceRoute;
        if (state.routes.length > 1 && state.index > 0) {
          const oldIndex = state.index - 1;
          // remove one that we are replacing
          state.routes.splice(oldIndex, 1);
          // index now one less
          state.index = oldIndex;
        }
      }
    }

    return state;
  };
};

export default updateStackRouter;

@digitalmaster
Copy link

Any movement here?.. just trying to do the same thing i did in ex-navigation like so: this.props.navigator.replace(Router.getRoute('routeName'))

@joshuapinter
Copy link

@cyberdelphos' corrected method works great with the latest (beta.11) version of the library.

Thanks for posting that update here @cyberdelphos! 🙏 You saved me hours trying to figure out what's wrong with @spencercarli's original method.

@leesolway
Copy link

leesolway commented Sep 1, 2017

I really liked @bleonard approach to the problem. I would love to see more helpers using this method, or having this level of simplicity such as reset.

@ahanriat
Copy link

ahanriat commented Sep 6, 2017

Not sure if that's gonna work with nested navigators though

@bleonard
Copy link

I haven't tried with nested, but it's only modifying the one stack that it's dealing with, so I would think it was ok.

@DanielRamosAcosta
Copy link

Both @spencercarli and @bleonard solutions loses the transition animation, is there any way to preserve it? Thanks

@tommeier
Copy link

Doesn't work with nested routes unfortunately

@salujaharkirat
Copy link

@spencercarli thanks for sharing the code. Can you please help to understand how this would work with nested navigators? I am getting #1127 when I try this with nested routes

@salujaharkirat
Copy link

salujaharkirat commented Nov 4, 2017

I was exploring this out a bit more and here are few things which one should be aware before implementing:-

  1. This does not work with nested routes.
  2. This does not work with tab routes.
  3. If you are trying to replace more than once screen using the code shared above you are liking to get an unidentified error message for key being missing.

To avoid point 3 I had changed my code to following:-

const defaultGetStateForAction = MainNavigation.router.getStateForAction;

MainNavigation.router.getStateForAction = (action, state) => {
  if (state && action.type === "replace") {
    const routes = state.routes.slice(0, state.routes.length - 1);
    action.key =  `id-${Date.now()}-${routes.length - 1}`;
    routes.push(action);
    return {
      ...state,
      routes,
      index: routes.length - 1
    };
  }
  return defaultGetStateForAction(action, state);
};

@gianpieroc
Copy link

@jinxac what about nested routes? Any solution?

@salujaharkirat
Copy link

@gianpieroc i had used nested-routes when i started the porting of code to react-navigation but after seeing the open issues related to nesting i completing avoided it and created a single stack navigator itself. So currently no solution for nested routes either.

@parlir
Copy link

parlir commented Mar 8, 2018

For those who have nested do the following (note pullat is a lodash function but basically you just need to remove second to last element)
@gianpieroc
@jinxac

const prevGetStateForActionMainStack = MainStack.router.getStateForAction;
MainStack.router.getStateForAction = (action, state) => {
  if (state && action.type === "ReplaceCurrentScreen") {
    action.type = "Navigation/NAVIGATE"; // change it to navigate to get the next state
    let newState = prevGetStateForActionMainStack(action, state); // Let default run because nesting hard to build
    pullAt(newState.routes, [newState.routes.length - 2]); // pull out second to last element
    newState.index = newState.index - 1;
    return newState;
  }
  return prevGetStateForActionMainStack(action, state);
};

@brentvatne
Copy link
Member

https://reactnavigation.org/docs/navigation-prop.html#replace

@pacozaa
Copy link

pacozaa commented Apr 2, 2018

@brentvatne still no example in there :(

@ericvicenti
Copy link
Contributor

@pacozaa, feel free to add a link to the navigation playground code in this repository which demonstrates the replace functionality, and send a pull request for that 🙂

@wv1124
Copy link

wv1124 commented Apr 3, 2018

ExceptionsManager.js:63 navigation.state.routes[2].key "scene_OrderList" conflicts with another route!

@b-asaf
Copy link

b-asaf commented Apr 11, 2018

Can replace() method be used to replace stacks, instead of using reset()?
currently, to replace stacks I am doing something like this:
import { NavigationActions } from 'react-navigation';

export default(navigation, stackName, screenName, params = {}) => {
    const resetAction = NavigationActions.reset({
        index: 0,
        key: null,
        actions: [
            NavigationActions.navigate({ routeName: stackName }),
        ],
    });
    navigation.dispatch(resetAction);
    if (screenName) {
        navigation.navigate(screenName, params);
    }
};

@martin-braun
Copy link

Has this been implemented into react-navigation, eventually, so I don't have to hack around the API to get this to work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests