React Navigation: I Like Your Style

Note: This is Part 2 in a series on React Navigation. Check out my other posts on Getting Up and Running, Stack, Tab, and Drawer Navigators, Custom Transition Animations, and Redux Integration.

Let’s talk about style.

Out of the box, React Navigation feels a little bland. We can change that. Almost everything about the navigation header is configurable: colors, fonts, shadows, buttons…heck, we could even replace the entire header with our own component if we wanted.

In this post, we’ll go through basic styling, as well as using setParams() to dynamically alter the header based on user input. I’m going to assume you have React Navigation installed and a basic StackNavigator set up. If not, check out my previous post to get up to speed. Ready? Ok!


Styling The Header

I’ve created a simple app that pretends to solve the problem of choosing something fun to do among a group of friends. It has two screens: A ‘Home’ screen with a list of events, and an ‘Event’ screen with details about who’s going. I’ve pre-loaded it with some dummy data and added some color to the screens, but haven’t touched the header yet.

Here’s my data:

const users = {
daniel: { name: 'Daniel' },
chris: { name: 'Chris' },
zack: { name: 'Zack' },
carlo: { name: 'Carlo'}
}
const events = [
{
name: 'Monster Truck Rally',
description: 'VROOM',
attendees: [users.daniel, users.chris]
},
{
name: 'Cat Café',
description: 'MEOW',
attendees: [users.carlo, users.daniel, users.zack, users.chris]
},
{
name: 'Atomic Pinball',
description: 'TILT',
attendees: [users.chris, users.zack, users.carlo]
},
{
name: 'Breakdancing Museum',
description: 'SPIN',
attendees: [ users.zack, users.daniel]
}
]

And here’s what it looks like:

That header is pretty boring, right? Let’s add some color. In our HomeScreen component, add a static class member named navigationOptions . Within this object, you can set the title of your screen and style your header. I’ll keep it simple and edit only my background color and my text color (check the docs for the complete set of editable properties):

// HomeScreen.js
static navigationOptions = {
title: 'What to do today?',
headerStyle: {
backgroundColor: '#16a085', <-- darkish green
},
headerTitleStyle: {
color: 'white'
},
}

Now let’s refresh and admire our handiwork:

Neat, a green header with white text. Let’s tackle that second screen while we’re at it. I’ll add the following to my EventScreen component:

// EventScreen.js
static navigationOptions = ({ navigation }) => ({
title: navigation.state.params.event.name,
headerStyle: {
backgroundColor: '#e74c3c', // <-- orangey red
},
headerTitleStyle: {
color: 'white',
},
headerBackTitleStyle: {
color: 'white'
},
})

Wait, why does that one look different than the first example? What’s up with the function syntax and the ({ navigation }) param?

The navigationOptions property can either be a plain old object, or it can be a function. In the latter case, it’ll get passed an object that includes the navigation prop, which we can grab data from (again, see my previous post if this concept is fuzzy). In our case, we’ve passed an event from the previous screen, and are using event.name to set the title.

Notice that headerBackTitleStyle property I set above. The default color for the ‘Back’ button text is blue on iOS, but I wanted it to look nice on the red header so I changed it to white.

If the title of my home screen was short enough, React Navigation wouldn’t display ‘Back’, but would use the title instead (i.e. if the title was something like ‘List’ or ‘Home’). The title of our HomeScreen is ‘What to do today?’, which is a pretty long string, so it gets truncated to ‘Back’. We can customize this, too.

In our HomeScreen, we’ll tell React Navigation what text to use instead of ‘Back’ . Since going back signals that we’re not interested in the event, we’ll set its headerTruncatedBackTitle to say ‘Nah’.

Notice that a screen sets its own truncated back button text, even though that text is displayed in whatever screen the user navigates to next. So even though we see ‘Nah’ on the EventScreen header, we set it to ‘Nah’ in HomeScreen. This is a bit counterintuitive at first, but imagine the back button as an extension of the screen before and it makes more sense.

// HomeScreen.js
static navigationOptions = {
title: 'What to do today?',
headerStyle: {
backgroundColor: '#16a085',
},
headerTitleStyle: {
color: 'white'
},
headerTruncatedBackTitle: 'Nah', // <-- set back button text
}

Hey, that’s looking pretty good. Let’s add a ‘like’ button in the top right of the header, and then call it a day.


Header Buttons

We want a button in the header that a user can press to add themselves to the list of friends interested in an event. Since this post is about navigation I’m not going to wire up the button to a persistent data source, so any changes will be lost as soon as we navigate away, but we’ll add some feedback to the screen when a user presses it and pretend that it gets stored in a database somewhere.

First, lets tackle the look of the button.

navigationOptions allows us to assign any component to a property called headerRight, which will be rendered to the right of the title. I created a round ‘Like’ button with a <TouchableOpacity /> and gave it some style, including a bit of a drop shadow. I felt that the header was getting a little crowded, so I bumped up its height to 85.

headerStyle: {
backgroundColor: '#e74c3c',
height: 85, // <-- bumped up header height
},
headerRight: (
<TouchableOpacity
style={{
height: 45,
width: 45,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(250, 250, 250, 0.7)',
borderRadius: 50,
margin: 5,
shadowColor: 'black',
shadowOpacity: 0.5,
shadowOffset: {
width: 2,
height: 2,
}
}}
>
<Text style= {{ fontSize: 30, color: '#2980b9'}}>
✌︎ // <-- tiny peace sign
</Text>
</TouchableOpacity>)
})

Cool, now lets tackle its functionality.

This part is slightly more involved than setting styles. We have to do some extra work to get the header and the main component talking to each other, even though they appear together on screen. We’ll use the navigation.setParams() helper function that react-navigation gives us to pass data from one to the other.

I left comments in the code below with more details, but the gist of it is that when we press the button, we’re going to pull the current list of attendees off of navigation.state.params, add ‘Me’ to the list of attendees, and then set the updated list as the data source for our component.

Our component displays names based on the contents of the event object we get from props.navigation.state.params.event. If we change the contents of event, we change what gets rendered.

So once we have our new updated event, we’ll pass it tonavigation.setParams({ event }), which will update props.navigation.state.params.event in our render() function, causing the component to display the new list.

Got it? Give that last paragraph a reread, it’s pretty dense.

...
<TouchableOpacity
onPress={() => {
    // Make a copy of the original event
const event = { ...navigation.state.params.event }
    // We don't want user to be able to add a million Me's to the 
// attendee list, so return early if 'Me' already exists
if (event.attendees.some(attendee => attendee.name === 'Me'))
return
    // Copy the original array and add 'Me'
event['attendees'] = [...event.attendees, { name: 'Me' }]
    // Pass an object containing 'event' to `navigation.setParams()`
// This will set `props.navigation.state.params.event`
// to the new value, which will cause render() to display the
// new list of names
    navigation.setParams({
event,
})
}}

...

The same pattern can also be used to change items in the header based on events that take place in the main screen too, since the navigation object with navigation.setParams() is available both places.

…and we’re done! Here’s our completed app:

When you press the button, ‘Me’ gets added to the list. Spamming the button doesn’t do anything.

Final code available at https://github.com/computerjazz/rn-styles

Check out my other posts about React Navigation: