RN-NavigationExperimental-Redux-Example icon indicating copy to clipboard operation
RN-NavigationExperimental-Redux-Example copied to clipboard

Nested routes / Tab reducer

Open arthurvi opened this issue 8 years ago • 9 comments

Awesome work, this example really helped me forward! Now i'm stuck at nested routes with Redux and the new NavigationExperimental. In this example there are three routes: 'first', 'second' and 'third'. What would the example look like if there were 2 modules/tabs with nested routes (first, second, third)?

For example:

  • Home
    • Home First
    • Home Second
    • Home Third
  • Settings
    • Settings First
    • Settings Second
    • Settings Third

I read about the tabreducer and I think this is applicable here, but I can't really figure out how to use it with Redux. Should there be a reducer per module/tab and then combined with combineReducers? Or should there be one reducer for all modules/tabs and their subroutes? Will this mean that there is a nested state tree?

Hope someone has an idea!

arthurvi avatar Mar 17 '16 13:03 arthurvi

Great question @arthurvi, that's one I haven't dived into yet. If you get to it before me, take a look at how the relationship between parent and child nav states works in https://github.com/facebook/react-native/blob/master/Libraries/NavigationExperimental/NavigationStateUtils.js#L34. I do believe there is supposed to be a nested state tree when doing something like that, but not sure on the implementation yet.

Maybe there are tips in the example app? https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js

jlyman avatar Mar 17 '16 15:03 jlyman

Thanks for the tips, here are my thoughts.

First, the TabsExample has no nested state. But I think our answer partly lies in the CompositionExample https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js Only I don't really understand the nested reducers in the tabreducer array.

The current relationship (in Redux) between parent and child states is:

const parentState = {
    key: 'MainNavigation',
    index: 0,
    children: [
        { key: 'First' }
    ]
}

Nested routes would look something like this I guess:

const appState = {
    key: 'MainNavigation',
    index: 0,
    children: [{ 
        key: 'Home',
        index: 0,
        children: [{ 
            key: 'First'
        }, {
            key: 'Second'
        }]
    }, {
        key: 'Settings',
        index: 0,
        children: [{ 
            key: 'First'
        }, {
            key: 'Second'
        }]
    }]
}

Note: maybe when the children (First, Second) objects will contain multiple properties, we should use a normalized state tree. But for now I skip that.

Children are looked up by their keys. So for nested state we could do: var firstLevelState = NavigationStateUtils.get(appState, 'Home') And then var secondLevelState = NavigationStateUtils.get('firstLevelState', 'First')

But this requires some adjustment in the reducer. We should know at which "level" of state we should execute our actions (push, pop etc.) to return the new the state tree.

Currently

case NAV_PUSH:
        return NavigationStateUtils.push(state, action.state)

Just pushes the state it is given. With nested state this is not possible. We have to determine whether the push should be "executed" at FirstLevel or at SecondLevel. I think the best way to do this is to keep all keys unique. Then supply a key parameter so that action.key is the string that indicates where the action should happen in the tree. We should use an array instead of a string when # of levels > 2.

So for example:

action.key = 'Settings'
case NAV_PUSH:
    If (action.keys) {
        firstLevelState = NavigationStateUtils.get(appState, action.key)
        newSecondLevelState = NavigationStateUtils.push(firstLevelState, action.state);
        return NavigationStateUtils.replaceAt(appState, action.key, newSecondLevelState)
    }
    return NavigationStateUtils.push(state, action.state)

When there are more levels of state, we should use an array of keys I think, but this requires more work.

Still, it does feel a little too much. What do you think?

arthurvi avatar Mar 18 '16 15:03 arthurvi

@arthurvi did you find any clean solution?

chogarcia avatar Apr 28 '16 00:04 chogarcia

@arthurvi I think you're on the right track. If you assume a state shape as you suggest, then it should be possible to generalize your approach to more levels with recursion. So for example, rather than this reducer

case NAV_PUSH:
    return NavigationStateUtils.push(state, action.state)

which would push onto the root only, we could supply a rootKey that we'd like to push onto

case NAV_PUSH:
    return generalizedNavigate(
        state, 
        action.rootKey, 
        (state) => NavigationStateUtils.push(state, action.state)
    )

and a helper reducer function could generate a new navigationState, based off a key it should be operating off of (e.g. 'Settings') and a function to generate a new state:

function generalizedNavigate(state, key, construct)

    // base case, no children
    if (!state.children) return state;

    // if we're at the right state, construct a new one
    else if (state.key === key) return construct(state);

    // otherwise, loop through children and recurse
    const newState = Object.assign({}, state);
    for (let i = 0; i < newState.children.length; i++) {
        newState.children[i] = generalizedNavigate(newState.children[i], key, construct);
    }
    return newState;

In terms of containers, I assume you would have nested NavigationViews that are passed the portion of navigationState that they care about (e.g. 'Settings' would only get the Settings subtree).

I'm pretty new to RN so I'm not sure how this works out in more complex navigation schemes. There may also be a cleaner way to generate navigationState than the function above. Appreciate any feedback!

wsun avatar May 07 '16 03:05 wsun

@arthurvi They pushed an example of a composed navigation 2 days ago. Maybe that'll help you? https://github.com/facebook/react-native/commit/1dc33b5f23640a60682ac879b9a3e94a4aa519d9

jgrancher avatar Jun 09 '16 05:06 jgrancher

On jun. 9 2016, at 7:41 am, Jérémy Grancher <[email protected]> wrote:

@arthurvi They pushed an example of composed navigation 2 days ago. Maybe that'll help you?
[facebook/react-native@1dc33b5](https://github.com/facebook/react- native/commit/1dc33b5f23640a60682ac879b9a3e94a4aa519d9)


You are receiving this because you were mentioned.
Reply to this email directly, [view it on GitHub](https://github.com/jlyman /RN-NavigationExperimental-Redux-Example/issues/4#issuecomment-224804926), or [mute the thread](https://github.com/notifications/unsubscribe/ADr3alsdnZ4KJes TB2DX37dWm8w_V4zLks5qJ6edgaJpZM4Hy7LS).![](https://github.com/notifications/be acon/ADr3atJUoe5d5NAXPCdwJhdv5EmlpVRwks5qJ6edgaJpZM4Hy7LS.gif)

arthurvi avatar Jun 09 '16 07:06 arthurvi

https://github.com/S4KH/react-native-nav-exp. Hope this one helps

s4kh avatar Jun 24 '16 02:06 s4kh

Here is another one https://github.com/tkjone/react-native-examples

athomasoriginal avatar Jun 29 '16 13:06 athomasoriginal

I also made an example covering android https://github.com/S4KH/react-native-nav-exp

s4kh avatar Jun 29 '16 14:06 s4kh