|
From React Native in Action by Nader Dabit This article discusses organizing styles in React Native within components. |
Save 37% on React Native in Action. Just enter code fccdabit into the discount code box at checkout at manning.com.
React Native comes with many built-in components, and the community has built many more which you can include with your projects. Each component supports a specific set of styles. Those styles may be applicable to other types of components. For example, the Text
component supports the fontWeight
property (fontWeight
refers to the thickness of the font). Conversely, the View
component supports the flex
property (flex
refers to the layout of components within a View
).
Some styling elements are similar between components but not exactly the same. For example, the View
component supports the shadowColor
property, whereas the Text
component supports the textShadowColor
property. Some styles, like shadowPropTypesIOS
, only apply to a specific platform.
Learning the various styles and how to manipulate them takes time. This is why it’s important to start with fundamentals like how to apply and organize styles. This section focuses on teaching those styling fundamentals, to provide you with a good foundation from which you can start exploring styles and building our example Profile Card component.
For a solid reference on how to make your mobile apps usable, see Matt Lacey’s Usability Matters.
Applying styles in your applications
To compete in the marketplace, mobile applications need a sense of style. You can develop a fully functional app, but if it looks terrible and isn’t engaging, people aren’t going to be interested. You don’t need to build the hottest looking app in the world, but you must commit to creating a polished product. A polished, sharp-looking app greatly influences people’s perception of the app’s quality.
You can apply styles to elements in React Native in a number of ways. Two common options are to use inline styling (listing 1) and styling using a StyleSheet
(listing 2).
Listing 1 Using inline styles
import React, { Component } from 'react' import { Text, View } from 'react-native' export default class App extends Component { render () { return ( <View style={{marginLeft: 20, marginTop: 20}}> ❶ <Text style={{fontSize: 18,color: 'red'}}>Some Text</Text> ❷ </View> ) } }
❶ An inline style applied to a React Native component.
❷ Multiple inline styles applied at once
As you can see in listing 2, it’s possible to specify multiple styles at once by supplying an object to the styles property.
Listing 2 Referencing styles defined within a StyleSheet
import React, { Component } from 'react' import { StyleSheet, Text, View } from 'react-native' export default class App extends Component { render () { return ( <View style={styles.container}> ❶ <Text style={[styles.message,styles.warning]}>Some Text</Text> ❷ </View> ) } } const styles = StyleSheet.create({ container: { ❸ marginLeft: 20, marginTop: 20 }, message: { ❸ fontSize: 18 }, warning: { color: 'red' ❸ } });
❶ Referencing the container
style defined within the styles
stylesheet.
❷ Using an array to reference both the message
and warning
styles from the stylesheet
❸ Defining the styles using StyleSheet.create
Functionally, there’s no difference between using an inline style versus referencing a style defined within a StyleSheet
. With StyleSheet
, you create a style object and refer to each style individually. Separating the styles from the render
method makes the code easier to understand and promotes reuse of styles across components.
When using a style name like warning
, it’s easy to recognize the intent of the message. An inline style which is color: 'red'
offers no insight into why the message is red. Having styles specified in one place rather than inline on many components makes it easier to apply changes across the entire application. Imagine that we wanted to change warning messages to yellow. All we need to do is change the style definition once in the stylesheet, color: 'yellow'
.
Listing 2 also shows you how to specify multiple styles by supplying an array of style properties. Remember when doing this that the last style passed overrides the previous style if there’s a duplicate property. For example, if an array of styles like…
style={[{color: 'black'},{color: 'yellow'},{color: 'red'}]}
…is supplied, then the last value for color overrides all the previous values. In this example, that color is red. It’s also possible to combine the two methodologies; specifying an array of styling properties using inline styles and references to stylesheets:
style={[{color: 'black'}, styles.message]}
React Native is flexible in this regard, which can be both good and bad. Specifying inline styles when you’re quickly trying to prototype something is extremely easy, but in the long haul, you’ll want to be careful how you organize your styles; otherwise your application can quickly become a mess and difficult to manage.
By organizing your styles, you’ll make it easier to
- maintain your application’s code base,
- reuse styles across components, and
- experiment with styling changes during development.
Organizing styles
As you might suspect from the previous section, using inline styles isn’t the recommended way to go: stylesheets are a much more effective way to manage styles. But what does that mean in practice?
When styling web sites, we use stylesheets all the time. Quite often we use tools like Sass, Less, and PostCSS to create monolithic stylesheets for our entire application. In the world of the web, our styles are global, but that isn’t the React Native way.
React Native focuses on the component. The goal is to make components as reusable and standalone as possible. Having a component dependent upon an application’s stylesheet is the antithesis of modularity. In React Native, styles are scoped to the component – not to the application.
How to accomplish this encapsulation depends entirely upon your team’s preference. No right or wrong way exists, but in the React Native community, you’ll find two common approaches:
- declaring stylesheets within the same file as the component
- declaring stylesheets in a separate file, outside of the component.
Declaring stylesheets in the same file as the component
A popular way to declare styles is within the component that uses them. The major benefit of this is that the component and its styles are completely encapsulated within a single file. This component can then be moved or used anywhere in the app. This is a common approach to component design, one that you see often within the React (Native) community.
When including the stylesheet definitions with the component, the typical convention is to specify the styles after the component.
Declaring stylesheets in a separate file
If you’re used to writing CSS, putting your styles into a separate file might seem like a better approach and feel more familiar. The stylesheet definitions are created in a separate file. You can name it whatever you want (styles.js
is typical) but be sure the extension is “.js”; it’s JavaScript after all. The stylesheet file and component file are saved in the same folder.
Figure 1 An example file structure with styles separated from components into a single folder instead of a single file.
A file structure like figure 1 retains the close relationship between components and styles, and affords a bit of clarity by not mixing style definitions in with the functional aspects of the components. Listing 3 corresponds to a styles.js file which is used to style a component like ComponentA
and ComponentB
above.
Listing 3 Externalizing a component’s stylesheets (styles.js)
import { StyleSheet } from 'react-native' const styles = StyleSheet.create({ ❶ container: { ❷ marginTop: 150, backgroundColor: '#ededed', flexWrap: 'wrap' } }) const buttons = StyleSheet.create({ ❸ primary: { ❹ flex: 1, height: 70, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center', marginLeft: 20, marginRight: 20 } }) export { styles, buttons } ❺
❶ Creating a stylesheet and saving it in the styles
constant. Try to make the constant names meaningful. In this case, styles
represent the general styles used by the component. The next constant’s named buttons
, because it defines styles used by buttons in the component.
❷ Defining a style for the container
. It can be referenced by the component as styles.container
.
❸ Creating a second stylesheet and saving it in the buttons
constant.
❹ Defining a style for the primary button. It can be referenced by the component as buttons.primary
.
❺ Exporting both the styles
and buttons
stylesheets, to endure that the component has access to the constants.
The component imports the external stylesheets and can reference any styles defined within them.
Listing 4 Importing external stylesheets
import { styles, buttons } from './component/styles' ❶ <View style={styles.container}> ❷ <TouchableHighlight style={buttons.primary} /> ❸ ... </TouchableHighlight> </View>
❶ Importing multiple stylesheets exported from styles.js
❷ A reference to the styles.container
style created in styles.js
❸ A reference to the buttons.primary
style created in styles.js
Styles are code
You’ve already seen how JavaScript is used to define styles in React Native. Despite having a full scripting language with variables and functions, our styles are rather static, but they certainly don’t have to be!
Web developers have fought with CSS for years. Entirely new technologies like Sass, Less, and PostCSS were created to work around the many limitations of cascading stylesheets. Even something like defining a variable to store the primary color of a site was impossible without CSS preprocessors. The CSS Custom Properties for Cascading Variables Module Level 1 candidate recommendation in December, 2015 introduced the concept of custom properties, which are akin to variables, but as I write this, fewer than 80% of browsers in use support this functionality.
Let’s take advantage of the fact that we’re using JavaScript and start thinking of our styles as code. Let’s build an application that gives the user a button to change the theme from light to dark. But before we start coding, let’s walk through what we’re trying to build.
The application has a single button on the screen. That button is enclosed by a small square box. When the button is pressed, the themes toggle. When the light theme is selected, the button label says WHITE, the background is white, and the box around the button is black. When the dark theme is selected, the button label is BLACK, the background is black, and the box around the button is white.
Look at Figure 2 to see what the screen should look like when the themes are selected.
Figure 2 A simple application that supports two themes, white and black. Users can click the button to toggle between a white background and a black background.
For this example, let’s organize our styles into a separate file, styles.js. Then, let’s create some constants to hold our color values, and create two stylesheets for the light and dark themes.
Listing 5 Dynamic stylesheets extracted from the main component file (styles.js)
import {StyleSheet} from 'react-native'; export const Colors = { ❶ dark: 'black', light: 'white' }; const baseContainerStyles = { ❷ flex: 1, justifyContent: 'center', alignItems: 'center' }; const baseBoxStyles = { ❸ justifyContent: 'center', alignItems: 'center', borderWidth: 2, height: 150, width: 150 }; const lightStyleSheet = StyleSheet.create({ ❹ container: { ...baseContainerStyles, backgroundColor: Colors.light }, box: { ...baseBoxStyles, borderColor: Colors.dark } }); const darkStyleSheet = StyleSheet.create({ ❺ container: { ...baseContainerStyles, backgroundColor: Colors.dark }, box: { ...baseBoxStyles, borderColor: Colors.light } }); export default function getStyleSheet(useDarkTheme){ ❻ return useDarkTheme ? darkStyleSheet : lightStyleSheet; ❼ }
❶ A constant defining the colors that corresponds to our light and dark themes. If we wanted to change our theme colors, we only need to change them in one spot!
❷ A simple JavaScript object to hold our base container styles
❸ A simple JavaScript object to hold our base box styles
❹ Create the stylesheet for the light theme
❺ Create the stylesheet for the dark theme
❻ A function that returns the appropriate theme based upon a Boolean value
❼ Return the dark theme if useDarkTheme
is true, otherwise return the light theme.
Once the styles are configured, we can start building our component App. Because we only have a light and dark theme, we created a utility function, getStyleSheet
, which takes a Boolean value. If true
is supplied, the dark theme is returned, otherwise the light theme is returned.
Listing 6 An application that toggles between a light and dark theme (App.js)
import React, { Component } from 'react'; import { Button, StyleSheet, View } from 'react-native'; import getStyleSheet from './styles'; ❶ export default class App extends Component { constructor(props) { super(props); this.state = { darkTheme: false ❷ }; this.toggleTheme = this.toggleTheme.bind(this); ❸ } toggleTheme() { this.setState({darkTheme: !this.state.darkTheme}) ❹ }; render() { const styles = getStyleSheet(this.state.darkTheme); ❺ const backgroundColor = StyleSheet.flatten(styles.container).backgroundColor; ❻ return ( <View style={styles.container}> ❼ <View style={styles.box}> ❽ <Button title={backgroundColor} ❾ onPress={this.toggleTheme}/> ❿ </View> </View> ); } }
❶ Import the getStyleSheet
function from our externalized styles
❷ Initialize our component’s state to show the light theme by default.
❸ Bind the toggleTheme
function to the component. Without binding the function to the component, this
is undefined, and you’ll get exceptions when trying to run the code.
❹ Toggle the theme value in state whenever the function’s called.
❺ Use our imported getStyleSheet
function to get the appropriate stylesheet for whatever theme should be displayed.
❻ React Native provides the StyleSheet.flatten
utility function to convert the StyleSheet
object into a simple JavaScript object. Doing this transformation makes It much easier to get the backgroundColor
being used.
❼ Reference our theme’s container
style
❽ Reference our theme’s box
style (the box border around the button)
❾ A string representation of the color being used by the theme
❿ When the button’s pressed, call the toggleTheme
function to alternate from one theme to another.
Now that you have an application that toggles themes, feel free to experiment and take it a bit further. Try changing the light theme to a different color. You’ll notice how easy it is because the colors are defined as constants in one place. Try changing the button label in the dark theme to be the same color as the background instead of always white. Try creating an entirely new theme, or modify the code to support many different themes instead of only two – have fun!
If you’re interested in learning more about the book, check it out on liveBook here and see this slide deck.