I've just started taking Stephen Grider's React Native course, and he explains a lot of React and JSX concepts to people who might not be familiar with them. If you're like me and already know React, read on for just the info specific to React Native.
React Native Elements - The First Three
Let's have a look at this React Native Component:
import React from 'react';
import { Text, StyleSheet, View } from 'react-native';
const ComponentsScreen = () => {
const name = 'Daniel';
return (
<View>
<Text style={styles.headingStyle}>Getting started with React Native</Text>
<Text style={styles.greetingStyle}>My name is {name}</Text>
</View>
);
};
const styles = StyleSheet.create({
headingStyle: {
fontSize: 45
},
greetingStyle: {
fontSize: 20
}
});
export default ComponentsScreen;
A few new things here! Let's break down that React Native import statement.
Text
Any time we want to show some text to the user, we have to wrap in in a Text
component. Any text we try to display outside of this component will result in an error.
StyleSheet
To give our elements CSS styling, we create style objects (just like we would in React), put them all into a bigger object, and then pass that object into StyleSheet.create()
. You might be thinking, "Why can't we just define styles inline?" The good news is, we can! However, if we define a non-React-friendly property (like fontsize
), StyleSheet.create()
will catch it and throw an error for us. We miss out on this check if we define styles inline.
View
If we want to render multiple JSX elements, we have to wrap them in a View
component. In React we could have just used div
tags, but those would result in an error in React Native.
FlatLists
When we want to render a list of JSX elements from an array in React, we're used to using a map
method. In React Native, we have to switch gears and make use of an element called FlatList
.
import React from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
const ListScreen = () => {
const friends = [
{ name: "Friend A" },
{ name: "Friend B "},
{ name: "Friend 4" },
]
return <FlatList data={friends} renderItem={element => {
//some code here
}}/>;
};
const styles = StyleSheet.create({});
export default ListScreen;
Looking at this element, the data
attribute is easy enough to grok, but what about the renderItem
attribute? Is that the same as a map
function? Unfortunately, no. In the first iteration of renderItem
, element
would look like this:
{
item: { name: "Friend A" },
index: 0
}
If we're only interested in the item
value, we can use a bit of ES6 destructuring:
const ListScreen = () => {
const friends = [
{ name: 'Friend A' },
{ name: 'Friend B ' },
{ name: 'Friend 4' }
];
return (
<FlatList
data={friends}
renderItem={({ item }) => {
return <Text>{item.name}</Text>;
}}
/>
);
};
Of course, just like in React, we have to define a key
attribute every time we render a list. If we don't feel like declaring a key
property for every item in our friends
array, we can take advantage of React Native's keyExtractor
method:
return (
<FlatList
keyExtractor={friend => friend.name}
data={friends}
renderItem={({ item }) => {
return <Text style={styles.textStyle}>{item.name}</Text>;
}}
/>
);
I know: "Ughhh, why can't we just use the map method?" Well, the upshot of FlatList
is that we can add a couple of attributes to easily turn the list horizontal (think Netflix tiles!). Check it out:
return (
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
keyExtractor={friend => friend.name}
data={friends}
renderItem={({ item }) => {
return <Text style={styles.textStyle}>{item.name}</Text>;
}}
/>
);
Buttons
In the beginning of React Native, a component called TouchableOpacity
was the sole equivalent of a button. It's powerful and allows for a lot of customization. However, it's not super intuitive, and a lot of developers starting out with React Native got hung up looking for a Button
component, so the React Native team added one. It's essentially a simpler version of TouchableOpacity
.
Button components
A React Native Button
component, unlike an HTML button, is a self-closing element. To render text inside it, we pass in a string as a title
attribute.
import React from 'react';
import { Text, StyleSheet, View, Button } from 'react-native';
const HomeScreen = () => {
return (
<View>
<Text style={styles.text}>Hi there!</Text>
<Button title='Go to Components Demo' />
</View>
);
};
A nice benefit of Button
components is that they render with some styling right out of the box: blue text on iOS or white text on a blue background on Android.
TouchableOpacity
A TouchableOpacity
, on the other hand, comes with no styling out of the box, save for a momentary fade-out effect when it is pressed. To make things confusing, TouchableOpacity
is NOT a self closing component, and we have to render some other element or elements between the tags.
import React from "react";
import { View, Text, Button, TouchableOpacity } from "react-native";
const HomeScreen = () => {
return (
<View>
<Text style={styles.text}>Hi there!</Text>
<Button title='Go to Components Demo' />
<TouchableOpacity>
<Text>Go to List Demo</Text>
</TouchableOpacity>
</View>
);
};
Adding functionality
In React, we're used to giving buttons an onClick
attribute with a callback function. We don't have a mouse to click with when we're using our phones, so the React Native equivalent is called onPress
. Here's what that might look like using the navigation
method from react-navigation-stack (specifics later in the article!):
import React from "react";
import { View, Text, StyleSheet, TextInput } from "react-native";
const HomeScreen = ({ navigation }) => {
return (
<View>
<Text style={styles.text}>Hi there!</Text>
<Button
onPress={() => navigation.navigate('Components')}
title='Go to Components Demo'
/>
<TouchableOpacity onPress={() => navigation.navigate('List')}>
<Text>Go to List Demo</Text>
</TouchableOpacity>
</View>
);
};
Images
React Native also has a primitive element for images called Image
. To render a local image, we pass the relative path into a require()
function and assign it to the source
attribute. Be careful: this is the full word source
, not src
!
import React from "react";
import { View, Text, Image } from "react-native";
const ImageDetail = props => {
return (
<View>
<Image source={require('../../assets/beach.jpg')} />
<Text>{props.title}</Text>
</View>
);
};
One catch is that source has to be a static value. That means can’t write <Image source={require(props.img)} />
— instead, we’d have to pass down the entire require('../../assets.beach.jpg')
function call as a prop.
Inputs
Adding a text input element seems pretty easy at first glance:
import React from "react";
import { View, TextInput } from "react-native";
const TextScreen = () => {
return (
<View>
<TextInput />
</View>
);
};
However, if we run this file as is, it’ll look like a blank screen. The TextInput
is actually there and we can interact with it, but it has zero default styling: no borders, no background, nothing. Let’s take care of that now:
const TextScreen = () => {
return (
<View>
<TextInput style={styles.input} />
</View>
);
};
const styles = StyleSheet.create({
input: {
margin: 15,
borderColor: "black",
borderWidth: 1
}
});
In addition, phones have auto-capitalize and autocorrect features we might not want to apply to our input. They’re both easy enough to disable:
const TextScreen = () => {
return (
<View>
<TextInput
style={styles.input}
autoCapitalize='none'
autoCorrect={false}
/>
</View>
);
};
You might’ve expected autoCapitalize
to be a boolean, but it’s not because we actually have a few capitalization schemes to choose from ( sentences
, characters
, words
).
Next, we’re going to want to make this input a controlled input. In other words, we want to connect the input’s value and the component’s state, just like in React. In React, we have an event listener attribute called onChange
and we set the state equal to event.target.value
. In React Native, that listener is called onChangeText
and simply receives a newValue
as a parameter instead of an entire event object.
const TextScreen = () => {
const [name, setName] = useState("");
return (
<View>
<Text>Enter name:</Text>
<TextInput
style={styles.input}
autoCapitalize='none'
autoCorrect={false}
value={name}
onChangeText={newValue => setName(newValue)}
/>
<Text>My name is: {name}</Text>
</View>
);
};
One last note: the React Native equivalent of onSubmit
for text inputs is onEndEditing
.
Navigation with react-navigation
I originally wasn’t going to talk about specific libraries in this article, but I figured anyone reading this would probably be wondering about it.
The react-navigation library is now in v5 with breaking changes, but yarn add react-navigation
installed v4 when I ran it. Apparently v5 has been released but is still in beta or something. The react-navigation has documentation for upgrading to v5 if you’d like. Anywho, assuming you’ve created a React Native app with the Expo CLI, you can run this command to get some helper libraries:
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
Also, we’ll need to install these:
yarn add react-navigation-stack @react-native-community/masked-view
Now we can get coding! First, we write a couple of imports:
import { createAppContainer } from "react-navigation";
import { createStackNavigator } from "react-navigation-stack";
Note that createStackNavigator
used to be in react-navigation, but now we have to import it from react-navigation-stack.
To get started, we declare a constant called navigator
(by convention) and assign it a createStackNavigator
call. This function takes two arguments, both objects: the first lists all our route names with their respective components, and the second defines other options.
Let’s tackle that first object argument. Assume we create a component called SearchScreen
. To make our app open to the search screen, we import it into App.js and assign it to some (relevant) key in our object:
{
Search: SearchScreen
}
Then, to make our app open up to SearchScreen
when it launches, we specify so in the second object. We can define other things as well, like the title we’d like in our header.
{
initialRouteName: "Search",
defaultNavigationOptions: {
title: "Business Search"
}
}
Finally, we export our whole component by passing it in to createAppContainer
. The whole App.js file would like this:
import { createAppContainer } from "react-navigation";
import { createStackNavigator } from "react-navigation-stack";
import SearchScreen from "./src/screens/SearchScreen";
const navigator = createStackNavigator(
{
Search: SearchScreen
},
{
initialRouteName: "Search",
defaultNavigationOptions: {
title: "Business Search"
}
}
);
export default createAppContainer(navigator);
So now how do we actually perform navigation? Easy enough: our child components are now going to receive navigation
as a prop, and that prop contains a navigate
method. If we wanted to navigate back to SearchScreen
, we’d simply pass the string 'Search'
into navigation.navigate()
. Hopefully this example from earlier makes more sense now:
import React from "react";
import { View, Text, StyleSheet, TextInput } from "react-native";
const HomeScreen = ({ navigation }) => {
return (
<View>
<Text style={styles.text}>Hi there!</Text>
<Button
onPress={() => navigation.navigate('Components')}
title='Go to Components Demo'
/>
<TouchableOpacity onPress={() => navigation.navigate('List')}>
<Text>Go to List Demo</Text>
</TouchableOpacity>
</View>
);
};
Hope this helps!
Top comments (0)