Currently I have both Push and Pop set up using NavigationStateUtils using React Native/Redux. But when a button that triggers the Push action is pressed more than once, I get the error: should not push * route with duplicated key
and * representing route.key
or this.props.navKey
.
What may be the cause of the error? How should I go about creating a unique key for each individual route using NavigationStateUtils
?
This is my set up - Redux:
function mapStateToProps(state) {
return {
navigation: state.navReducer,
}
}
export default connect(
mapStateToProps,
{
pushRoute: (route) => push(route),
popRoute: () => pop(),
}
)(NavigationRoot)
My reducer (navReducer.js
):
const initialState = {
index: 0,
key: 'root',
routes: [{
key: 'login',
title: 'Login',
component: Login,
direction: 'horizontal',
}]
}
function navigationState (state = initialState, action) {
switch(action.type) {
case PUSH_ROUTE:
if (state.routes[state.index].key === (action.route && action.route.key)) return state
return NavigationStateUtils.push(state, action.route)
case POP_ROUTE:
if (state.index === 0 || state.routes.length === 1) return state
return NavigationStateUtils.pop(state)
default:
return state
}
}
export default navigationState
And these methods handle push and pop and how navigation bar back (pop) button is set up:
_renderScene (props) {
const { route } = props.scene
return (
<route.component _handleNavigate={this._handleNavigate.bind(this)} {...route.passProps} actions={this.props}/>
)
}
_handleBackAction() {
if (this.props.navigation.index === 0) {
return false
}
this.props.popRoute()
return true
}
_handleNavigate(action) {
switch (action && action.type) {
case 'push':
this.props.pushRoute(action.route)
return true
case 'back':
case 'pop':
return this._handleBackAction()
default:
return false
}
}
renderOverlay = (sceneProps) => {
if(0 < sceneProps.scene.index)
{
return (
<NavigationHeader
{...sceneProps}
renderLeftComponent={() => {
switch(sceneProps.scene.route.title){
case 'Home':
return (
<TouchableHighlight onPress={() => this._handleBackAction()}>
<Text}>X</Text>
</TouchableHighlight>
)
}
}
}
/>
)
}
}
render() {
return (
<NavigationCardStack
direction={this.props.navigation.routes[this.props.navigation.index].direction}
navigationState={this.props.navigation}
onNavigate={this._handleNavigate.bind(this)}
renderScene={this._renderScene}
renderOverlay={this.renderOverlay}
/>
)
}
And called by components like so:
const route = {
home: {
type: 'push',
route: {
key: 'home',
title: 'Home',
component: Home,
direction: 'vertical',
}
}
}
EDIT console log
EDIT 2 Continuation
EDIT 3 Slide Menu
Put simply, navigation refers to the ability to move from one page to another. In this React Router and Redux tutorial, we’ll show you the nuances of navigating within your React/Redux applications and demonstrate how to do so declaratively.
Redux is a predictable state container designed to help you write JavaScript apps that behave consistently across client, server, and native environments and are easy to test. While it’s mostly used as a state management tool with React, you can use it with any other JavaScript framework or library.
if a route with the given name already exists in the state, StackRouter will jump to the existing route, along with setting the new parameters. If, however, you want to push several instances of the same route, you can do so by providing a unique key parameter each time you call navigate, or you can use the push action available on StackRouter.
What is React Router? React Router is a popular declarative way of managing routes in React applications. It takes away all of the stress that comes with manually setting routes for all of the pages and screens in your React application. React Router exports three major components that help us make routing possible — Route, Link, and BrowserRouter.
Debug your navigation reducer like this:
function navigationState (state = initialState, action) {
switch(action.type) {
case PUSH_ROUTE:
console.log('action', action);
if (state.routes[state.index].key === (action.route && action.route.key)) return state
const newNavigationState = NavigationStateUtils.push(state, action.route);
console.log('newNavigationState', newNavigationState);
return newNavigationState;
case POP_ROUTE:
if (state.index === 0 || state.routes.length === 1) return state
return NavigationStateUtils.pop(state)
default:
return state
}
}
EDIT
Because of the way NavigationStateUtils.push
works (see here), it requires a completely new route to push to the route stack. Based on your navigation flow, you cannot use it again to go back home, you have to use a different function like NavigationStateUtils.pop
or NavigationStateUtils.reset
and others as well (explore that file). However, you are not limited to just the functions in NavigationStateUtils
, you can define your own modification in the reducer but you need to make sure the navigation state has this data format:
{
// `routes` needs to be an array of objects and each object
// needs to have a unique `key` property
routes: [{
key: 'anything', // must be unique in this `routes` array
...anyOtherData
}],
// `index` is required, and has to be a number that refers
// to the index of the route in the routes array that is active
index: 1,
}
EDIT
Based on your requirements in your comments, every time your hit a drawer item, it needs to reset your navigation route stack having that route as your starting point, then you can start pushing and popping.
function navigationState (state = initialState, action) {
switch(action.type) {
case PUSH_ROUTE:
if (state.routes[state.index].key === (action.route && action.route.key)) return state
return NavigationStateUtils.push(state, action.route)
case POP_ROUTE:
if (state.index === 0 || state.routes.length === 1) return state
return NavigationStateUtils.pop(state)
case DRAWER_ITEM_PRESS:
// `action.route` is simply the route object of the drawer item you pressed
return NavigationStateUtils.reset(state, [action.route], 0);
default:
return state;
}
}
As another side note, I see that based on your requirements, it would probably be a good idea to try using react-native-router-flux
because you will be able to define your routes, sub-routes, integrate with react-native-drawer
and integrate with Redux.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With