Marc
Marc

Reputation: 7022

Navigation Experimental: How to change the Navigation Title based on NavigationCard content

Sometimes, the title of the Navigation Header will be determined by the content of the NavigationCard. E.g. in What's App, you will see the username and photo when you are in the chat window.

I'm wondering how to do this, since the NavigationHeader is the parent of the NavigationCard.

    <NavigationAnimatedView
            navigationState={navigationState}
            style={styles.container}
            onNavigate={(action) => {
                if (action.type === 'back') {
                    navigateBack();
                }
            }}
            renderOverlay={this._renderHeader.bind(this)}
            renderScene={props => (
                <NavigationCard
                    {...props}
                    key={props.scene.navigationState.key}
                    ref="sceneRef"
                    renderScene={this._renderScene.bind(this)}
                />
            )}
        />

    <NavigationHeader
            {...props}
            navigationState = {navigationState}
            viewProps={props}
            style={[styles.appbar]}
            renderTitleComponent={() => this._renderTitle(route)}
            renderLeftComponent={() => this._renderHeaderLeft(route)}
            renderRightComponent={() => this._renderHeaderRight(route)}
        />

I tried creating a ref on the NavigationCard, so I can call the component containing the information, but that is not available yet in the renderTitleComponent method.

Upvotes: 3

Views: 703

Answers (4)

Richard Lindhout
Richard Lindhout

Reputation: 2068

It works to change the title in the reducer. Keep in mind that routes.slice() does not clone the objects inside the array. The function below works with the standard header logic etc

  case t.CHANGE_ROUTE_TITLE:{

  let routes = state.routes.slice()
  let i = routes.length - 1
  routes[i] = {
    ...routes[i],
    title: action.payload,
    searchPlaceholder: 'Zoek in ' + action.payload
  }

  return {
    ...state,
    index: routes.length - 1,
    routes,
  };
}

Upvotes: 0

neciu
neciu

Reputation: 4485

My solution to this problem is quite simple but I'm not sure if it's not too hacky. Only thing you need to change is reducer part, so you keep changes in one place. I just dynamically change title from state as PUSH/POP action appears:

const initialState = {
    index: 0,
    routes: [
        {key: 'cities', title: 'Cities'},
    ],
};

export const reducer = (state = initialState, action) => {
    switch (action.type) {
        case '@@redux/INIT': {
            return {...state, title: state.routes[0].title};
        }
        case actionTypes.PUSH: {
            // some additional logic
            const newState = {...state, title: action.newState.title};
            return NavigationStateUtils.push(newState, action.newState);
        }
        case actionTypes.POP: {
            // some additional logic
            const newState = {...state, title: state.routes[state.index - 1].title};
            return NavigationStateUtils.pop(newState);
        }
    }
    return state;
};

Note that I'm listening on @@redux/INIT action. You can ommit this by manually setting title to initialState:

const initialState = {
    index: 0,
    routes: [
        {key: 'cities', title: 'Cities'},
    ],
};

Upvotes: 1

Mike Lambert
Mike Lambert

Reputation: 1986

The first part, is in your onNavigate that navigates to that card, you should pass a title in your state, which will automatically be pulled and used for your header component: this.props.onNavigate({key: 'NewView', title: myuser.Username})}

The second part, I see that you are overriding renderTitleComponent. Which means you will be responsible for passing in the title yourself (or any other data you want). Luckily, you can still read it from your scene's navigationState (same way as is done in the default renderTitleComponent code), by doing something like this:

renderTitleComponent={(props) => {
  return <NavigationHeaderTitle>
    {props.scene.navigationState.title
  </NavigationHeaderTitle>;
}}

In this case, you may want to pass in the user itself in the navigationState object, so you can grab the profile photo as well.

Upvotes: 1

Daniel Basedow
Daniel Basedow

Reputation: 13396

I had the same problem. As far as I know there is no best practice to do this documented anywhere. I added a custom navigation reducer that will set the title for the current scene.

function CustomReducer(lastState, action) {
    switch (action.type) {
        case 'updateTitle':
            let newState = {...lastState, children: lastState.children.slice()};
            newState.children[lastState.children.length - 1].title = action.title;
            return newState;

    }
    return lastState;
}

Now you can do stuff like this:

componentWillMount() {
    this.props.onNavigate({type: 'updateTitle', title: 'Foo'});
}

I don't like calling onNavigate for something that is clearly not navigation related, but it is extremely simple and works well so far.

I would love to see some other approaches as well.

Upvotes: 1

Related Questions