The Problem
If there is one thing that most react native apps have in common, it is how they display text. Some simple apps show very little, but most apps display a lot. That is why it's vital to put the right system in place, to ensure adding and refactoring texts in your app is as simple as possible.
Now, you might be thinking - why am I reading an article on how to manage such a simple component? It's just text, right? Yes, but more often than not, you need to apply styles and properties to said text to make it look the way you want. When you have to do it for all the texts in your app, it adds up. This, of course, begs the question - how can I reduce the amount of code repetition?
Over the years, I have seen my fair share of attempts at solving this problem, and I can finally say that I have landed on one solution that left me quite pleased.
The Solution
The solution is quite simple, but it's based on following best UI practices. What do I mean? Having predefined styles for headings, paragraphs, and other texts that you might need. What is important is that all the texts of your app fall into one of those definitions and that the amount doesn't get out of hand. It's good to keep the number of definitions short, so it's easier to remember what to put when coding and that your app is consistent in its respective styling, which in turn, gives you usability and accessibility points.
Without any further ado, here is the aforementioned code of the component and supplementary files:
// constants/fonts.js | |
/* | |
Define font constants so they are easy to refactor | |
if you need to change your pimary font | |
*/ | |
export const PRIMARY_FONT_REGULAR = 'NunitoSans-Regular'; | |
export const PRIMARY_FONT_SEMI = 'NunitoSans-SemiBold'; | |
export const PRIMARY_FONT_BOLD = 'NunitoSans-Bold'; | |
/* -------------------------------------------------------------- */ | |
// components/Text/index.js | |
export { default } from './Text'; | |
/* -------------------------------------------------------------- */ | |
// components/Text/Text.js | |
import React from 'react'; | |
import { arrayOf, instanceOf, oneOf, oneOfType, string } from 'prop-types'; | |
import { Text as RNText } from 'react-native'; | |
import styles from './Text.styles'; | |
// AVAILABLE TEXT TYPES | |
/* | |
Define all your text types here and add them to the typeShape. | |
That way they are validated with the prop-type checker. | |
*/ | |
const H1 = 'H1'; | |
const H2 = 'H2'; | |
const H3 = 'H3'; | |
const H4 = 'H4'; | |
const P1 = 'P1'; | |
const P2 = 'P2'; | |
const P3 = 'P3'; | |
export const typeShape = oneOf([H1, H2, H3, H4, P1, P2, P3]); | |
/* | |
You can set extra styles that add or override to the default ones. | |
You can also pass any other RN Text component props. | |
*/ | |
const Text = ({ children = '', as = P1, style, ...restProps }) => ( | |
<RNText style={[styles.base, styles[as], style]} {...restProps}> | |
{children} | |
</RNText> | |
); | |
Text.propTypes = { | |
children: oneOfType([string, instanceOf(Text), instanceOf(RNText)]), | |
as: typeShape, | |
style: oneOfType([arrayOf(RNText.propTypes.style), RNText.propTypes.style]), | |
}; | |
export default Text; | |
/* -------------------------------------------------------------- */ | |
// components/Text/Text.styles.js | |
import { StyleSheet } from 'react-native'; | |
import { | |
PRIMARY_FONT_REGULAR, | |
PRIMARY_FONT_BOLD, | |
PRIMARY_FONT_SEMI, | |
} from '..(relative_path)/constants/fonts'; | |
// Import color from your color constants file | |
import { BLACK } from '..(relative_path)/constants/colors'; | |
/* | |
For any common styles you can add them to base. | |
Define a rule group for each of the types defined in Text.js | |
*/ | |
const styles = StyleSheet.create({ | |
base: { color: BLACK }, | |
H1: { | |
fontFamily: PRIMARY_FONT_BOLD, | |
fontSize: 30, | |
lineHeight: 41, | |
}, | |
H2: { | |
fontFamily: PRIMARY_FONT_SEMI, | |
fontSize: 24, | |
lineHeight: 33, | |
}, | |
H3: { | |
fontFamily: PRIMARY_FONT_BOLD, | |
fontSize: 16, | |
lineHeight: 20, | |
}, | |
H4: { | |
fontFamily: PRIMARY_FONT_BOLD, | |
fontSize: 14, | |
lineHeight: 19, | |
}, | |
P1: { | |
fontFamily: PRIMARY_FONT_REGULAR, | |
fontSize: 16, | |
lineHeight: 20, | |
}, | |
P2: { | |
fontFamily: PRIMARY_FONT_REGULAR, | |
fontSize: 14, | |
lineHeight: 19, | |
}, | |
P3: { | |
fontFamily: PRIMARY_FONT_REGULAR, | |
fontSize: 10, | |
lineHeight: 14, | |
}, | |
}); | |
export default styles; |
Once you have all of that in place, you can simply import the component and use it as follows:
// MyCustomScreen.js | |
import { TouchableOpacity } from 'react-native'; | |
import Text from '..(relative_path)/components/Text'; | |
const MyCustomScreen = () => ( | |
<> | |
<Text as="H1">This is the main title</Text> | |
<Text as="H2">This a secondary title</Text> | |
<Text>This a normal paragraph</Text> | |
<TouchableOpacity onPress={() => {}}> | |
<Text as="H3" style={{ color: 'red'}}>Press Me!</Text> | |
</TouchableOpacity> | |
</> | |
); | |
export default MyCustomScreen; |
It's as simple as that, and usually, you will only have to apply extra styles to a particular text component instance if you need to add spacing, color, or wrapping.
Taking the naming convention and hierarchy of text components from the web makes it very easy to understand and remember. As you may have noticed, with the use of prop-types, it is very easy to notice if you misspelled a type when setting it on an instance of said component.
Although adding custom fonts to your React Native project is out of scope for this article, you might find (link here) this article useful where we cover that subject (How to customize fonts in React Native)
Conclusion
As you can see, the solution is very clean and simple, but it fixes a problem in a component that is so basic that it is also often overlooked. Putting a bit of time and thought into these kinds of components that make up the core DNA of our apps can go a long way.
I hope you find this article helpful and that you end up implementing this solution in your projects.
Top comments (1)
Amazing article!