Overview
I finally decided to see what all the fuss is about and create a demo mobile app for react native with typescript. Let’s just say I have a very strong opinion on why you should use Flutter over react native. Leaving that aside, to me a mobile app should consist of these minimum requirements:
- Call some web services.
- Handle decent amount of metadata.
- Offer navigation, a minimum of 3 screens.
- Handle multimedia, in my case, Video.
For Part I, I will be going over setup and concepts you will need to be familiar with to start coding for react native.
Setup
Tools
For tools I personally use Visual Studio Code, VSCode for short.
Environment
Node.js
You will need to install node.js.
React Native
React Native’s official site already has a guide and will strongly recommend following it here.
You will have to install a CLI for react native, there’s two choices, React Native CLI and Expo CLI, since we want to mimic production as much as possible. Expo CLI adds quite the overhead to projects, so instead will recommend to install React Native CLI. This does require us to configure Android Studio and XCode for mobile support, which is all in the tutorial page linked.
Installing cocoapods
You will need to install cocoapods at some point. The easiest way is using homebrew. You can install homebrew by following their guide.
Once you have it installed run the following command:
homebrew install cocoapods
If for some reason you have conflicts while installing cocoapods like me, just install fresh version of ruby. Here’s some reason why this is the recommended way.
After that big hassle you can finally run ruby commands and run:
sudo gem install cocoapods
Creating the Project
Since I already wasted so much time with the setup and react offers project templates, we will be using the TS template. To do so run the following command, make sure to change the name to whatever you may want:
npx react-native init MyReactNativeTSProject --template react-native-template-typescript
This will start a setup wizard, just follow the instructions to automagically create your project.
With the project already created, we need to learn a bit on how React Native works and its structure.
React Components
Components are UI elements that can be created as class or functions. For a more in detail and interactive tutorial go here. One rule however, is that a file containing components must import react:
import React from 'react';
Here’s an example of a class component:
export default class CarouselPage<T extends ICarouselItem> extends Component<
Props<T>,
State
> {
state: State = {
isLoading: false,
};
render() {
const data = Array.from(this.props.source).map(([key, value]) => {
return {title: key, data: value};
});
return (
<SectionList
sections={data}
renderItem={() => null}
renderSectionHeader={({section}) => (
<>
<Text style={styles.header}>{section.title}</Text>
<FlatList
data={section.data}
horizontal
renderItem={({item}) => item.getItemView()}></FlatList>
</>
)}
/>
);
}
}
Here I’ve created a class component to display a Carousel of content, or movies in this case.
We can see that a component is composed of the method render. This one should return a JSX element. These element are nothing but custom html-ish elements. You may also notice that we need to use parenthesis: ( ) in our return when using JSX elements adn curly braces: { } to define logic within a JSX annotations.
For comparison, here’s a function component:
const Home = () => {
const [isLoading, setIsLoading] = useState(true);
const [movieMap, setMovieMap] = useState(new Map<string, ICarouselItem[]>());
const navigator =
useNavigation<NativeStackNavigationProp<StackParams, 'MovieDetails'>>();
const OnItemClicked: OnItemClicked = (movie: Movie) => {
navigator?.navigate('MovieDetails', {movie: movie});
};
const getMovies = async () => {
try {
const popularMovies = await getPopularMovies();
const topMovies = await getTopRatedMovies();
const nowPlayingMovies = await getNowPlayingMovies();
console.log(`Movies loaded.`);
const movieDictionary = new Map<string, ICarouselItem[]>();
movieDictionary.set(
'Popular',
popularMovies.map(movie => new MovieCarouselItem(movie, OnItemClicked)),
);
movieDictionary.set(
'Top Rated',
topMovies.map(m => new MovieCarouselItem(m, OnItemClicked)),
);
movieDictionary.set(
'Now Playing',
nowPlayingMovies.map(m => new MovieCarouselItem(m, OnItemClicked)),
);
setMovieMap(movieDictionary);
} catch (e) {
console.error(e);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
getMovies();
}, []);
return (
<SafeAreaView style={Styles.container}>
{isLoading ? <ActivityIndicator /> : <CarouselPage source={movieMap} />}
</SafeAreaView>
);
};
Don’t worry we will be going over the different elements. If you paid attention, there’s no render method in the function version of the component. Instead we have a return statement at the end.
React Props
Props include all properties or rather parameters for a class or function component. You may have noticed that in the Home function component references CarouselPage as a JSX and has a Prop called source. We are essentially passing the collection of movies we fetched.
For a more detailed tutorial on props please follow the following links:
React Hooks
Hooks are essentially callback functions used to notify and update your component state. Let’s use code from our app
const [isLoading, setIsLoading] = useState(true);
const [movieMap, setMovieMap] = useState(new Map<string, ICarouselItem[]>());
Here you may have noticed that we use the useState hook. This one allows us to control our component’s state. We defined two in this case, isLoading and movieMap, but also notice that useState hook returns an array of 2 elements. The left side is the actual variable that will contain the value we are interested in and the right one is the setter function on which to update the value.
Now let’s see how to update our state:
const getMovies = async () => {
try {
const popularMovies = await getPopularMovies();
const topMovies = await getTopRatedMovies();
const nowPlayingMovies = await getNowPlayingMovies();
console.log(`Movies loaded.`);
const movieDictionary = new Map<string, ICarouselItem[]>();
movieDictionary.set(
'Popular',
popularMovies.map(movie => new MovieCarouselItem(movie, OnItemClicked)),
);
movieDictionary.set(
'Top Rated',
topMovies.map(m => new MovieCarouselItem(m, OnItemClicked)),
);
movieDictionary.set(
'Now Playing',
nowPlayingMovies.map(m => new MovieCarouselItem(m, OnItemClicked)),
);
setMovieMap(movieDictionary);
} catch (e) {
console.error(e);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
getMovies();
}, []);
return (
<SafeAreaView style={Styles.container}>
{isLoading ? <ActivityIndicator /> : <CarouselPage source={movieMap} />}
</SafeAreaView>
);
In the method getMovies we are trying to go and fetch curated lists of movies. Eventually we see that at the end of the try block we use the setter to update our movieMap. Then in our finally block, we set isLoading to false. This in turn triggers our component to be redrawn, calling render once more.
If we move to our return statement, we will see that we have a conditional based on our state variable isLoading, whcih controls a loading indicator or rendering the CarouselPage.
You may have also noticed another hook here, useEffect. This hook allows us to run side code, in our case call getMovies.
Debugging
At some point you will encounter a bug and will need debugging, this is the best video out there:
That’s it for Part I, if you will like to get ahead, all code is accessible at GitHub. Thanks for following along,
Happy Coding!