At the first part of this guide we figured how to run our very first React Native application and learned about some fundamental concepts for building our user interfaces. You can check it here: Running our first RN app.
As said in this post, this full guide will cover these:
- Initial React Native project configuration
- React Native Navigation
- Redux
- Firebase Firestore
First step is already completed, as we built our ReactNativeNotes project and deployed it to our devices for the first time. Now we need to understand how or initial project is configured in order to finally start the coding part.π€
We'll work this guide to get this:
For starters, it's extremely important to understand how our project is configured. With our initial configuration, our root directory looks like this:
We can see we have three initial folders inside:
π ReactNativeNotes
π _tests_
π android
π ios
π node_modules
π§ Important note: Both ios and android folders are projects by theirselves, android folder can be opened in android studio. For ios we can open .xcodeproj or .xcworkspace in Xcode. I recommend to use .xcworkspace as it doesn't present problems for running our project. If this file doesn't exist yet, you need to install Pods to your iOS project.
The node_modules folder contains every package we have installed and is necessary for running our app. Its content is built after the package.json file. Every external dependency we install using a package manager like yarn or npm will be reflected in this file under the "dependecies" section. This is how our package.json looks like right now:
Next important file is index.js located at the root of our project:
This file is what the bundler will be looking for to compile our application. As we can see, the method registerComponent is used to define what will be compiled, receiving as first parameter the name of our application and as second parameter our main component, which in this case is our App component imported from our App.js file.
Once these things are noted, we'll get our hands on the code!π¨βπ»
Coding! π»
First thing will need to do is creating a new directory at the root of our project, we'll name it src
. Inside this folder we will create three more folders, the first one will be named components
, the second one will be named screens
and the last one will me named utils
. Our new project structure should look like this:
We did this in order to organize our project classifying which components will serve as screens and which ones will be to build a specific part of our UI.
Let's start with our screens. Create a new folder inside src/screens/
named notes
. Here you'll create two files: index.js and styles.js
Now we are finally going to start typing. At index.js you'll need to add this:
import React, { Component } from 'react';
import {
SafeAreaView,
View,
Text,
StatusBar,
} from 'react-native';
const initState = {
}
class NotesScreen extends Component {
constructor(props) {
super(props)
this.state = {
...initState
}
}
render() {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<View style={{ justifyContent: 'center', alignItems: 'center' }}>
<Text>My notes</Text>
</View>
</SafeAreaView>
</>
);
}
};
export default NotesScreen;
With this lines of code we will initiate our main screen using a class component. Now it's a good idea to take a look of the types of components we'll be using. π§
Components π
As said before, the magic of React Native is that it is all about components. We need to understand that each of this components has its own lifecycle from the moment it is "summoned" to our screen. We'll take a quick look to the lifecycle methods we'll use:
-
constructor()
: This is the first method to be executed when calling a component. Here's where we usually set all variables and also fetch all external data we'll be using within our component. -
render()
: This method is executed after constructor. It is in charge of displaying our UI. -
componentDidMount()
: This method is executed after rendered, and its execution is constantly active. It can listen to changes within our component and is regularly used to set events that will be used along the whole component lifecycle. -
componentWillUnmount()
: This method is executed when the lifecycle of our component reaches its end.
Now it's important to understand that we can use two types of components:
- Class Components: This type of components provide an easier way to handle our lifecycles, we can use every method from a component inside this type. We'll be using this type for the our screens.
- Functional Components: These are plain javascript functions which return a JSX structure. We'll be using them to render specific parts of our UI where we don't really need to deal with lifecycle methods.
βοΈWe can handle component lifecycles in Functional Components using React Hooks. You should take a look at that.
Let's change our App.js file!
App.js contains a functional component named App, which will return a JSX structure. This JSX structure is our NotesScreen from screens folder.
After doing this, your app should look like this:
Great! We've learned how to import a component and render it inside another one.π€ Now it's time to build our user interface with styles.
Go to utils folder and add two files called colors.js
and globalStyles.js
. We'll use this to centralize our styles in a certain location and make it easier to manage within our components, this truly helps when building large scale applications saving a lot of time of copying and pasting code:
Now let's go back to our notes screen folder and update both index.js and styles.js to the following:
π A state in React is an object which belongs to our components context, it is composed by the "variables" we'll be using within this context. Its values can be modified by using setState()
method.
Now our screen now looks like this:
It's looking kinda better isn't it!
Let's understand the code above:
// IMPORTS SECTION
import React, { Component } from 'react';
import {
SafeAreaView,
TextInput,
View,
Text,
StatusBar,
TouchableOpacity,
} from 'react-native';
// Importing Styles
import styles from "./styles";
import globalStyles from "../../utils/globalStyles";
The first block of code is about importing everything we'll need to build our interface. We import the components we'll need from react-native
to build the structure of our UI, and import styles to customize and adapt these components to our needs.
_textChangeHandler = (text) => {
this.setState({ newNote: text })
}
<TextInput
style={styles.input}
onChangeText={text => this._textChangeHandler(text)}
multiline
placeholder={'Write your note here'}
value={this.state.newNote}
/>
TextInput component receives multiline prop to allow writing on multiple lines inside, an onChangeText props which receives _textChangeHandler function, this function sets newValue state value received from function's call.
_clearNoteHandler = () => {
this.setState({ newNote: '' })
}
_saveNoteHandler = () => {
const notes = this.state.savedNotes // create copy from current state value
notes.unshift({ note: this.state.newNote, done: false }) // set new note to the top of the array
this.setState({ savedNotes: notes, newNote: '', notesFlag: !this.state.notesFlag }) // set states with update values
}
<TouchableOpacity onPress={this._clearNoteHandler}>
<Text style={styles.clearButtonText}>Clear</Text>
</TouchableOpacity>
<TouchableOpacity
style={this.state.newNote === '' ? styles.saveButtonDisabled : styles.saveButton}
disabled={this.state.newNote === ''}
onPress={() => this._saveNoteHandler()}
>
<Text style={styles.saveButtonText}>Save</Text>
</TouchableOpacity>
We created our buttons using TouchableOpacity components, these receive onPress props to perform actions once tap on them. Clear button receives a function which sets newNote state to an empty string and save note button receives a function which adds the newNote value to the current notes and then updates states values.
βοΈβοΈNote: Functions inside a class component must be called with prefix "this."
to be referred. The same applies to state values, this is necessary because these are part of this class component context.
Next step is showing saved notes array. We'll use Flatlist component to render our savedNotes state. Add FlatList to react-native
imports at the top of our file, then add the following code at notesContainer View:
<FlatList
extraData={this.state.notesFlag}
data={this.state.savedNotes}
style={{ width: '90%' }}
keyExtractor={(value, index) => index.toString()}
renderItem={value =>
<NoteComponent
note={value.item} doneHandler={() => this._checkDoneHandler(value.index)}
removeHandler={() => this._removeNoteHandler(value.index)} />
}
/>
Flatlist component receives some interesting props: data prop is the current data we'll be rendering, extraData prop is necessary in case the data we're currently rendering presents any changes, we update this flag to indicate Flatlist musts re render its content. KeyExtractor prop defines a unique key for each data value. renderItem prop receives a function which must return a JSX structure, in this case we'll be using a customized component which we must import at the top of the file.
We'll be able to mark our notes as done and remove them from our list, therefore we'll use these next functions to handle our note card actions:
_checkDoneHandler = (index) => {
const notes = this.state.savedNotes
if (notes[index].done) {
notes[index].done = false
notes.unshift(notes.splice(index, 1)[0])
}
else {
notes[index].done = true
notes.push(notes.splice(index, 1)[0])
}
this.setState({ savedNotes: notes, notesFlag: !this.state.notesFlag })
}
_removeNoteHandler = (index) => {
Alert.alert(
'Warning!',
'Are you sure you want to delete this note?',
[
{
text: 'No',
style: 'cancel'
},
{
text: 'Yes',
onPress: () => {
const notes = this.state.savedNotes
notes.splice(index, 1)
this.setState({ savedNotes: notes, notesFlag: !this.state.notesFlag })
},
},
],
{ cancelable: false }
);
}
_checkDoneHandler function receives the selected index and checks wether it was already checked or not and changes it position in both cases. _removeNoteHandler uses another component called Alert
which allows users to decide if they want to perform the action or not. If it is accepted, this function will remove the element from the position received from call.
As NoteComponent is a customized component, it will also receive customized props which will be passed at its invocation: note prop is the current data value to be rendered by our customized component, removeHandler and checkHandler are the functions already discussed above.
Let's build our NoteComponent to finally render our notes.πͺ
Create a new folder inside our components directory named note. Inside this folder create two more files: index.js and styles.js, just like before. These files will look like this:
We use a functional component, which only return a JSX structure. Our structure is designed to build a card like this:
In order to use those styled checkboxes we'll install a UI library called react-native-elements
.
Run in terminal yarn add react-native-elements
. After we've finished installing, we'll be able to use its checkboxes importing them like this:
import {CheckBox} from 'react-native-elements'
We'll render the card and wrap our notes content inside a <Text>
tag by calling the object note from props object:
<Text> {note} </Text>
π To use normal javascript inside a JSX code block, it's necessary to use { _our javascript code_ }
.
Once our component is done, both index.js should look like this:
NoteComponent:
NoteScreen:
Now we're finally able to run our app getting this result:
We've covered a lot of new concepts in this guide which are the basis to build almost every user interface. Next part will be about storing our notes to our cloud database with Firestore.
Here's the link to this repo if you want to check it out. Leave a comment if you had any problems, doubts or just say hi if you want to.π€
Keep in touch for the coming parts of this guide.
Thank you for reading!!
Top comments (0)