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

Nested StackReducers and BackAction #77

Open
domchristie opened this issue Jun 2, 2016 · 6 comments
Open

Nested StackReducers and BackAction #77

domchristie opened this issue Jun 2, 2016 · 6 comments

Comments

@domchristie
Copy link

I am using nested StackReducers to achieve the following navigation state (one for the 'main' stack, the other for the '_conversations' stack):

{
  key: 'main',
  index: 1,
  children: [
    { key: 'dashboard' },
    {
      key: '_conversations',
      index: 1,
      children: [
        { key: 'conversations' },
        { key: 'conversation' }
      ]
    }
  ]
}

I’d expect that dispatching { type: 'BackAction' } would pop the inner-most active state:

{
  key: 'main',
  index: 1,
  children: [
    { key: 'dashboard' },
    {
      key: '_conversations',
      index: 0,
      children: [
        { key: 'conversations' }
      ]
    }
  ]
}

But instead it affects both stacks resulting in:

{
  key: 'main',
  index: 0,
  children: [
    { key: 'dashboard' }
  ]
}

Is this the correct behaviour? If so, are there any techniques to prevent the action from being handled by an outer stack?

Thanks!

(Related: facebook/react-native#6963)

@jmurzy
Copy link

jmurzy commented Jun 2, 2016

@domchristie See facebook/react-native@807726b—NavigationExperimental has no intention to support hierarchical states.

I also think reducers belong to user-level code, and the ones currently shipped with NavigationExperimental should be simplified and moved to UIExplorer for reference impl. Otherwise supporting everyone's use cases is a very quick path to a very bloated codebase. Also, docs may be forthcoming.

@ericvicenti @hedgerwang What do you guys think?

🍺

@hedgerwang
Copy link
Collaborator

@jmurzy :
We're going to remove all Reducers from NavigationExperimental.

tl;dr;

For navigation actions at high level, reducers from NavigationReducers does not
know anything about the app-specific state thus people won't use these reducers.
Instead, people should build their own reducers.

There are a lot of good libraries available that help people to reducing things if that's
what they really need.

At the low level, for navigation state changes that don't involve app-specific state,
NavigationStateUtils should server that kind of need.

NavigationReducers serves very little benefit cause it does not know the app state, it does
not know how to traverse the navigation states which can be a tree, a list or a map.

That said, we hold no interest in owning in the core navigation library.

@jmurzy
Copy link

jmurzy commented Jun 2, 2016

@hedgerwang My thoughts exactly. Thanks!

🍺

@domchristie
Copy link
Author

@jmurzy @hedgerwang

Thanks for the clarification. I had seen facebook/react-native@807726b, and was aware that some of the NavigationExperimental parts were going to be deprecated (e.g. containers), but I wasn’t entirely sure if that would impact reducers (I had not seen facebook/react-native@69627bf until now).

That said, we hold no interest in owning in the core navigation library.

I think it’s a bit of a shame that the project has taken this stance, since even the most basic app will usually require some form of navigation. However, having worked on creating a navigation library for the app I’m working on, I can understand the reasoning—dealing with the multitude of navigation permutations is far from straightforward!

I look forward to checking out the new approach with the new examples :)

In the mean time, would it be worth clarifying the state of navigation in the README? It seems that a significant part of the API is deprecated (whether you use Navigator or NavigationExperimental), which makes it a bit difficult to know where to start.


For what it’s worth, I handled the Nested StackReducer + BackAction issue by wrapping the stack reducer in another reducer. Something like:

const stackReducer = NavigationExperimental.Reducer.StackReducer({})

function navigationReducer (state, action) {
  if (action.type === 'BackAction') return navigateBack(state, action)
  else return stackReducer(state, action)
}

navigateBack traverses the navigation state tree, until it reaches the inner-most active state. It then pops that state, and decrements the parent state’s index.

@hedgerwang
Copy link
Collaborator

@domchristie : navigate states don't necessarily need to be nested.
For instance, say that you have three tabs "apple", "banana" and "orange". Each tab has its won scenes.

image

So how would you model the navigation states?
One of the easy way to handle this is to use flatten navigation states which all navigation state is contained my a object map.

// First Step.
// Define what app navigation state will look like.
function createAppNavigationState(): Object {
  return  {
    // Three tabs.
    tabs: {
      index: 0,
      routes: [
        {key: 'apple'},
        {key: 'banana'},
        {key: 'orange'},
      ],
    },
    // Scenes for the `apple` tab.
    apple: {
      index: 0,
      routes: [{key: 'Apple Home'}],
    },
    // Scenes for the `banana` tab.
    banana: {
      index: 0,
      routes: [{key: 'Banana Home'}],
    },
    // Scenes for the `orange` tab.
    orange: {
      index: 0,
      routes: [{key: 'Orange Home'}],
    },
  };
}

then you can build the reducer around it.

// Next step.
// Define what app navigation state shall be updated.
function updateAppNavigationState(
  state: Object,
  action: Object,
): Object {
  let {type} = action;
  if (type === 'BackAction') {
    type = 'pop';
  }

  switch (type) {
    case 'push': {
      // Push a route into the scenes stack.
      const route: Object = action.route;
      const {tabs} = state;
      const tabKey = tabs.routes[tabs.index].key;
      const scenes = state[tabKey];
      const nextScenes = NavigationStateUtils.push(scenes, route);
      if (scenes !== nextScenes) {
        return {
          ...state,
          [tabKey]: nextScenes,
        };
      }
      break;
    }

    case 'pop': {
      // Pops a route from the scenes stack.
      const {tabs} = state;
      const tabKey = tabs.routes[tabs.index].key;
      const scenes = state[tabKey];
      const nextScenes = NavigationStateUtils.pop(scenes);
      if (scenes !== nextScenes) {
        return {
          ...state,
          [tabKey]: nextScenes,
        };
      }
      break;
    }

    case 'selectTab': {
      // Switches the tab.
      const tabKey: string = action.tabKey;
      const tabs = NavigationStateUtils.jumpTo(state.tabs, tabKey);
      if (tabs !== state.tabs) {
        return {
          ...state,
          tabs,
        };
      }
    }
  }
  return state;
}

the full snippet can be found at https://gist.github.com/hedgerwang/4c9cc24b729d0787a1558ff47f0ca439.

@joaovpmamede
Copy link

@hedgerwang sorry to bring this back but I'm having trouble in modelling some nested Navigations.
Basically I'd like to have some sort of Layout/Navigation like Whatsapp in which when you enter there's a Tab View and if you click on a Chat item the Tab View is replaced by other View (the Chat itself without the TabBar below it) which has it's own Navigation.
Inside the Chat we could then navigate to other Views or return to the Tab View.

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

4 participants