Skip to content

Commit 73ff16e

Browse files
committed
Add themes doc
1 parent 685ca04 commit 73ff16e

File tree

5 files changed

+476
-0
lines changed

5 files changed

+476
-0
lines changed

docs/themes.md

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
---
2+
id: themes
3+
title: Themes
4+
sidebar_label: Themes
5+
---
6+
7+
Providing a light theme and a dark theme is a nice way to let your users adjust the appearance of your app depending on the time of day or their preference. It also signals that you are a hip app developer that keeps up with the trends of the day.
8+
9+
Building themes into an app with React Navigation is not too much different than a React app without it; the main differences are that you will need to use `screenProps` in order to update style properties controlled by `navigationOptions`, and when style properties are controlled in navigator configuration we'll need to take another approach. First we're going to recap how you would theme a React app without React Navigation, then we will dive deeper into these differences. Additionally, this guide assumes that you are already comfortable with React Navigation, in particular how to use and configure navigators.
10+
11+
## Using React context to theme components
12+
13+
React's context API allows you to share state from an ancenstor component to any of its descendents without explicitly passing the value through layers and layers of components ("prop drilling"). This is a useful tool in order to build themes because we can define the theme at the root of the app, and then access it from anywhere else and re-render every themed component whenever the theme changes. If you are not familiar with how to use context already, you might want to read the [React documentation](https://reactjs.org/docs/context.html) for it before continuing.
14+
15+
```jsx
16+
import * as React from 'react';
17+
import { Text, TouchableOpacity, View } from 'react-native';
18+
19+
const ThemeContext = React.createContext(null);
20+
const ThemeConstants = {
21+
light: {
22+
backgroundColor: '#fff',
23+
fontColor: '#000',
24+
},
25+
dark: {
26+
backgroundColor: '#000',
27+
fontColor: '#fff',
28+
},
29+
};
30+
31+
export default class AppContainer extends React.Component {
32+
state = {
33+
theme: 'light',
34+
};
35+
36+
toggleTheme = () => {
37+
this.setState(({ theme }) => ({
38+
theme: theme === 'light' ? 'dark' : 'light',
39+
}));
40+
};
41+
42+
render() {
43+
return (
44+
<ThemeContext.Provider
45+
value={{ theme: this.state.theme, toggleTheme: this.toggleTheme }}>
46+
<HomeScreen />
47+
</ThemeContext.Provider>
48+
);
49+
}
50+
}
51+
52+
class HomeScreen extends React.Component {
53+
render() {
54+
return (
55+
<ThemedView
56+
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
57+
<ThemeContext.Consumer>
58+
{({ toggleTheme }) => (
59+
<ThemedButton title="Toggle theme" onPress={toggleTheme} />
60+
)}
61+
</ThemeContext.Consumer>
62+
</ThemedView>
63+
);
64+
}
65+
}
66+
67+
class ThemedButton extends React.Component {
68+
render() {
69+
let { title, ...props } = this.props;
70+
return (
71+
<TouchableOpacity {...props}>
72+
<ThemeContext.Consumer>
73+
{({ theme }) => (
74+
<Text style={{ color: ThemeConstants[theme].fontColor }}>
75+
{title}
76+
</Text>
77+
)}
78+
</ThemeContext.Consumer>
79+
</TouchableOpacity>
80+
);
81+
}
82+
}
83+
84+
class ThemedView extends React.Component {
85+
render() {
86+
return (
87+
<ThemeContext.Consumer>
88+
{({ theme }) => (
89+
<View
90+
{...this.props}
91+
style={[
92+
this.props.style,
93+
{ backgroundColor: ThemeConstants[theme].backgroundColor },
94+
]}
95+
/>
96+
)}
97+
</ThemeContext.Consumer>
98+
);
99+
}
100+
}
101+
```
102+
103+
Okay, that's a lot of code. There isn't much going on here aside from passing the theme around through context and then pulling it out of context when we need it inside of themed component. Themed components like `ThemedView` and `ThemedButton` are useful to help you avoid constantly repeating theme context related boilerplate.
104+
105+
## Themes inside `navigationOptions`
106+
107+
A regrettable limitation of the current implementation of `navigationOptions` is that we are unable to access React context for use in properties such as `headerStyle` and `headerTintColor`. We can and should use them in properties that access React components, for example in `headerRight` we could provide a component like `ThemedHeaderButton`. To apply the theme to other properties we need to use `screenProps`.
108+
109+
```jsx
110+
import { createAppContainer, createStackNavigator } from 'react-navigation';
111+
112+
class HomeScreen extends React.Component {
113+
static navigationOptions = ({ screenProps }) => {
114+
let currentTheme = ThemeConstants[screenProps.theme];
115+
116+
return {
117+
title: 'Home',
118+
headerTintColor: currentTheme.fontColor,
119+
headerStyle: { backgroundColor: currentTheme.backgroundColor },
120+
};
121+
};
122+
123+
render() {
124+
return (
125+
<ThemedView
126+
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
127+
<ThemeContext.Consumer>
128+
{({ toggleTheme }) => (
129+
<ThemedButton title="Toggle theme" onPress={toggleTheme} />
130+
)}
131+
</ThemeContext.Consumer>
132+
</ThemedView>
133+
);
134+
}
135+
}
136+
137+
const Stack = createStackNavigator({ Home: HomeScreen });
138+
const Navigation = createAppContainer(Stack);
139+
140+
export default class AppContainer extends React.Component {
141+
state = {
142+
theme: 'light',
143+
};
144+
145+
toggleTheme = () => {
146+
this.setState(({ theme }) => ({
147+
theme: theme === 'light' ? 'dark' : 'light',
148+
}));
149+
};
150+
151+
render() {
152+
return (
153+
<ThemeContext.Provider
154+
value={{ theme: this.state.theme, toggleTheme: this.toggleTheme }}>
155+
<Navigation screenProps={{ theme: this.state.theme }} />
156+
</ThemeContext.Provider>
157+
);
158+
}
159+
}
160+
```
161+
162+
Success! The stack header style now updates when the theme changes.
163+
164+
> Note: in the future we would like to deprecate `screenProps` and move entirely over to React context. For now, `screenProps` is the best way to do that, and when this changes it will be easy to migrate.
165+
166+
## Theming tabs and other similar navigators
167+
168+
Some navigators may have their styles configured in the navigator configuration object when they are initialized. While it may be best to update these navigators so that they can be configured more easily through `navigationOptions`, as long as they allow us to override the UI that they render with our own component and give us access to the default component, we can work with them just fine. We'll look at how to theme a bottom tab navigator.
169+
170+
```jsx
171+
import {
172+
createAppContainer,
173+
createStackNavigator,
174+
createBottomTabNavigator,
175+
BottomTabBar,
176+
} from 'react-navigation';
177+
178+
const ThemeConstants = {
179+
light: {
180+
backgroundColor: '#fff',
181+
fontColor: '#000',
182+
activeTintColor: 'blue',
183+
inactiveTintColor: '#ccc',
184+
},
185+
dark: {
186+
backgroundColor: '#000',
187+
fontColor: '#fff',
188+
activeTintColor: '#fff',
189+
inactiveTintColor: '#888',
190+
},
191+
};
192+
193+
// Notice how we override the `activeTintColor`, `inactiveTintColor` and
194+
// `backgroundColor` of the tab bar with our theme styles.
195+
class ThemedBottomTabBar extends React.Component {
196+
render() {
197+
return (
198+
<ThemeContext.Consumer>
199+
{({ theme }) => (
200+
<BottomTabBar
201+
{...this.props}
202+
activeTintColor={ThemeConstants[theme].activeTintColor}
203+
inactiveTintColor={ThemeConstants[theme].inactiveTintColor}
204+
style={{
205+
backgroundColor: ThemeConstants[theme].backgroundColor,
206+
}}
207+
/>
208+
)}
209+
</ThemeContext.Consumer>
210+
);
211+
}
212+
}
213+
214+
const Stack = createStackNavigator({ Home: HomeScreen });
215+
const Tabs = createBottomTabNavigator(
216+
{ Stack },
217+
{ tabBarComponent: ThemedBottomTabBar }
218+
);
219+
const Navigation = createAppContainer(Tabs);
220+
221+
// And the rest of the code goes here...
222+
```
223+
224+
You will likely want to go a bit further than we detailed in this guide, such as change the status bar color depending on the theme and customize the border color for the header and tab bar as well. You can see all of the above code plus some more changes to make it more complete in [this Snack](https://snack.expo.io/@react-navigation/themes-example).
225+
226+
I never said it was easy, but this about covers what you need to know to theme an app that uses React Navigation. Good luck, remember me you're a billionaire.

website/i18n/en.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"title": "Authentication flows",
2525
"sidebar_label": "Authentication flows"
2626
},
27+
"bottom-sheet": {
28+
"title": "bottom-sheet"
29+
},
2730
"bottom-tab-navigator": {
2831
"title": "createBottomTabNavigator",
2932
"sidebar_label": "createBottomTabNavigator"
@@ -86,6 +89,9 @@
8689
"title": "createDrawerNavigator",
8790
"sidebar_label": "createDrawerNavigator"
8891
},
92+
"dynamic-routes": {
93+
"title": "dynamic-routes"
94+
},
8995
"function-after-focusing-screen": {
9096
"title": "Call a function when focused screen changes",
9197
"sidebar_label": "Call a function when focused screen changes"
@@ -128,6 +134,9 @@
128134
"title": "Know when a screen is focused and blurred",
129135
"sidebar_label": "Know when a screen is focused and blurred"
130136
},
137+
"localization": {
138+
"title": "localization"
139+
},
131140
"material-bottom-tab-navigator": {
132141
"title": "createMaterialBottomTabNavigator",
133142
"sidebar_label": "createMaterialBottomTabNavigator"
@@ -257,6 +266,10 @@
257266
"title": "createTabNavigator",
258267
"sidebar_label": "createTabNavigator"
259268
},
269+
"themes": {
270+
"title": "Themes",
271+
"sidebar_label": "Themes"
272+
},
260273
"transitioner": {
261274
"title": "Transitioner",
262275
"sidebar_label": "Transitioner"
@@ -713,6 +726,10 @@
713726
"title": "Limitations",
714727
"sidebar_label": "Limitations"
715728
},
729+
"version-3.x-material-top-tab-navigator": {
730+
"title": "createMaterialTopTabNavigator",
731+
"sidebar_label": "createMaterialTopTabNavigator"
732+
},
716733
"version-3.x-modal": {
717734
"title": "Opening a full-screen modal",
718735
"sidebar_label": "Opening a full-screen modal"
@@ -777,6 +794,10 @@
777794
"title": "Tab navigation",
778795
"sidebar_label": "Tab navigation"
779796
},
797+
"version-3.x-themes": {
798+
"title": "Themes",
799+
"sidebar_label": "Themes"
800+
},
780801
"version-3.x-web-support": {
781802
"title": "React Navigation on the Web",
782803
"sidebar_label": "Web support"

website/sidebars.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"navigation-key",
2929
"deep-linking",
3030
"screen-tracking",
31+
"themes",
3132
"state-persistence",
3233
"redux-integration",
3334
"MST-integration",

0 commit comments

Comments
 (0)