|
@ -80,3 +80,4 @@ buddybuild_postclone.sh
|
|||
bin/test.js
|
||||
.github
|
||||
example
|
||||
codorials
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
# App Navigation
|
||||
|
||||
React Navigation has gone through many cycles of navigation implementations and has been a pain point for developers for a good while.
|
||||
A current "go to" navigation library is called [react-navigation](https://reactnavigation.org/). It's pure JavaScript implementation
|
||||
which performs well and provides a solid foundation for navigation on both Android and iOS.
|
||||
|
||||
In this step we'll be focusing on adding a Login & Register screen to our app.
|
||||
|
||||
## Installation
|
||||
|
||||
Simply install the dependency via NPM, no native installation is needed:
|
||||
|
||||
```bash
|
||||
npm install --save react-navigation
|
||||
```
|
||||
|
||||
## Navigation Stacks
|
||||
|
||||
Navigation on an app typically works in stacks, where a user can navigate to a new screen (pushing a new screen onto the stack), or backwards (popping
|
||||
a screen off the stack).
|
||||
|
||||
What's great about this concept is that we can create multiple instances of a stack, for example a stack for unauthenticated users and another for
|
||||
authenticated ones.
|
||||
|
||||
To create a new stack, we import the `StackNavigator` from `react-navigation`. In it's basic form, the first item of the `StackNavigator` object
|
||||
acts as our initial screen on the stack. Lets create a new directory and component for our unauthenticated state:
|
||||
|
||||
```js
|
||||
// src/screens/unauthenticated/index.js
|
||||
|
||||
import { StackNavigator } from 'react-navigation';
|
||||
|
||||
import Login from './Login';
|
||||
import Register from './Register';
|
||||
|
||||
export default StackNavigator({
|
||||
Login: {
|
||||
screen: Login,
|
||||
},
|
||||
Register: {
|
||||
screen: Register,
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
In both the `Login` & `Register` files, create a basic React component (change Login to Register where appropriate):
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
// src/screens/unauthenticated/Register.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
Login
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using the stack
|
||||
|
||||
StackNavigator returns a React component which can be rendered in our app. If we go back to our `src/App.js` component, we can now return
|
||||
the stack:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import UnauthenticatedStack from './screens/unauthenticated';
|
||||
|
||||
class App extends Component {
|
||||
|
||||
render() {
|
||||
return <UnauthenticatedStack />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
Our `UnauthenticatedStack` component will now show the `Login` component as it's the first item in the `StackNavigator`. Reload your app and you
|
||||
should have your `Login` component rendering!
|
||||
|
||||
![Basic Navigation](assets/1-unauthenticated-nav.jpg =300x*)
|
||||
|
||||
## Styling the navigator
|
||||
|
||||
As you can see, `react-navigation` provides basic styling to mimic the feel of Android's [Material Design](https://material.io). The
|
||||
library provides a simple, React like API to style and control your app.
|
||||
|
||||
> If you're using iOS, the functionality will remain the same however the basic styling will represent that of the iOS interface instead!
|
||||
|
||||
For this example we're going to add a title to our screen and liven up the colors - there's loads more you can do with `react-navigation` though,
|
||||
just check out their in-depth [documentation](https://reactnavigation.org/docs/getting-started.html).
|
||||
|
||||
Lets go ahead and style the screen, using a class static `navigationOptions` object which lets `react-navigation` access our screen component styling:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
|
||||
// Add our react-navigation static method:
|
||||
static navigationOptions = {
|
||||
title: 'Login',
|
||||
headerStyle: {
|
||||
backgroundColor: '#E6853E',
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
Login
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
||||
```
|
||||
|
||||
With this basic config you'll end up with an Android looking app with minimal configuration. Whats better is that `react-navigation` will also
|
||||
take care of any back buttons and screen animations when navigating through the stack, pretty nifty.
|
||||
|
||||
![Styled Navigation](assets/2-unauthenticated-nav.jpg =300x*)
|
||||
|
||||
## Pushing to a new stack
|
||||
|
||||
Pushing a new screen onto the stack is a common practice on mobile apps, however requires a slightly different mindset if you're from a web development
|
||||
background. The basics of a stack allow you to `push` and `pop` where screens effectively overlay each other. The user cannot change stack item
|
||||
unless you give them the ability to (compared to a website where the user could manually enter a different URL). This allows for greater
|
||||
control over what a user is able to push/pop to.
|
||||
|
||||
Each component we assign to our `StackNavigator` gets cloned by `react-navigation` with a prop called `navigation` which gives us full control over
|
||||
all of the navigation functionality we'll need.
|
||||
|
||||
- To "push" to a new screen we call the `navigate` method with the screen name we defined as the object key within `StackNavigator`.
|
||||
- To "pop", or go back to the previous screen on the stack we call the `goBack` method.
|
||||
|
||||
Lets add a simple button to push to the `Register` screen we defined:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
import React, { Component } from 'react';
|
||||
import { View, Button } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
|
||||
static navigationOptions = {
|
||||
title: 'Login',
|
||||
headerStyle: {
|
||||
backgroundColor: '#E6853E',
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
};
|
||||
|
||||
// Call this method on the button press
|
||||
_register = () => {
|
||||
this.props.navigation.navigate('Register');
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Button
|
||||
onPress={this._register}
|
||||
title="Register Now!"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
||||
```
|
||||
|
||||
Go ahead and click the button, you'll be pushed to a new screen. By pressing the back arrow on the header, `react-navigation` will automatically
|
||||
call the `goBack` method for us:
|
||||
|
||||
![Transition!](assets/3-unauthenticated-push-pop.gif =300x*)
|
||||
|
||||
> To style the `Register` page, simply add it's own `navigationOptions` static config!
|
||||
|
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 341 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 219 KiB |
After Width: | Height: | Size: 374 KiB |
|
@ -0,0 +1,28 @@
|
|||
# Conclusion
|
||||
|
||||
Hopefully from this Codorial you have seen the power of Firebase - allowing you to concentrate on your application and not worry about other complicated
|
||||
topics, such as authentication. However understanding how to integrate this into your main business logic can seem challenging at first. This Codorial
|
||||
has brought together a number of separate libaries which can be applied to any application context.
|
||||
|
||||
## Starter Kits
|
||||
|
||||
Invertase has provided a couple of starter kits to get your app up and running quicker:
|
||||
|
||||
### React Native Firebase Starter
|
||||
|
||||
https://github.com/invertase/react-native-firebase-starter
|
||||
|
||||
The React Native Firebase Starter is a free, pre-setup React Native app which integrates the [react-native-firebase](https://github.com/invertase/react-native-firebase)
|
||||
modules. It's perfect if you want to get up and running with no native module installation on both Android & iOS.
|
||||
|
||||
### React Native Authentication Starter Kit for Firebase
|
||||
|
||||
https://rnfirebase.io/kits/auth-starter
|
||||
|
||||
The React Native Authentication Starter Kit for Firebase is a premium starter kit for Android & iOS with a fully integrated authentication flow out of the box.
|
||||
It features email/password, Facebook, Google and Phone Number login, routing with [react-navigation](https://reactnavigation.org/) and Google Analytics for Firebase.
|
||||
|
||||
## Hire Us
|
||||
|
||||
The developers behind react-native-firebase at Invertase are available for hire on your next project, big or small. If you'd like to find out more,
|
||||
head over to our website and drop us a contact message: https://invertase.io/
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"title": "React Native authentication with Firebase",
|
||||
"description": "Create a React Native app from scratch, implementing authentication with react-native-firebase. This Codorial covers navigation, firebase, redux and more!",
|
||||
"tags": ["react-native", "redux", "react-redux", "firebase", "firebase-auth", "react-native-firebase", "react-navigation"],
|
||||
"steps": [
|
||||
{
|
||||
"title": "Getting Started",
|
||||
"file": "getting-started"
|
||||
},
|
||||
{
|
||||
"title": "Project Structure",
|
||||
"file": "project-structure"
|
||||
},
|
||||
{
|
||||
"title": "Understanding Firebase Auth",
|
||||
"file": "understanding-firebase-auth"
|
||||
},
|
||||
{
|
||||
"title": "Integrating Redux",
|
||||
"file": "integrating-redux"
|
||||
},
|
||||
{
|
||||
"title": "App Navigation",
|
||||
"file": "app-navigation"
|
||||
},
|
||||
{
|
||||
"title": "Handling Authentication State",
|
||||
"file": "handling-authentication-state"
|
||||
},
|
||||
{
|
||||
"title": "Creating a sign-in form",
|
||||
"file": "creating-a-signin-form"
|
||||
},
|
||||
{
|
||||
"title": "Facebook Login",
|
||||
"file": "facebook-login"
|
||||
},
|
||||
{
|
||||
"title": "Google Login",
|
||||
"file": "google-login"
|
||||
},
|
||||
{
|
||||
"title": "Conclusion",
|
||||
"file": "conclusion"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
# Creating a sign-in form
|
||||
|
||||
We can now get onto allowing the user to login with their email and password. First off we need to create a dummy user for testing. This can be
|
||||
done via the Firebase console on the 'Authentication' tab. Lets go ahead and create one now:
|
||||
|
||||
![Add new user](assets/add-user.jpg)
|
||||
|
||||
## Handling user input
|
||||
|
||||
React Native provides us with a [`TextInput`](https://facebook.github.io/react-native/docs/textinput.html) component, which renders the web
|
||||
equivalent of an `input` box in our app. `TextInput` components are 'uncontrolled' meaning we have to explicitly give it a value and handle
|
||||
updates the user enters. We're going to do this via component state, however you could also do this via our Redux store which is an option
|
||||
other React developers would go down.
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, TextInput } from 'react-native';
|
||||
|
||||
class Login extends Component {
|
||||
|
||||
static navigationOptions = {
|
||||
title: 'Login',
|
||||
headerStyle: {
|
||||
backgroundColor: '#E6853E',
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
}
|
||||
|
||||
_updateEmail = (email) => {
|
||||
this.setState({ email });
|
||||
};
|
||||
|
||||
_updatePassword = (password) => {
|
||||
this.setState({ password });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder={'Email Address'}
|
||||
onChangeText={this._updateEmail}
|
||||
value={this.state.email}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder={'Password'}
|
||||
onChangeText={this._updatePassword}
|
||||
value={this.state.password}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Login;
|
||||
```
|
||||
|
||||
If you reload your app, you will see two plain `TextInput` boxes which can accept input. As these are updated, the `onChangeText` prop is triggered
|
||||
which then updates state for that specific value. The inputs then individually update whenever their `value` from state changes:
|
||||
|
||||
![TextInput Changes](assets/textinput-update.gif =300x*)
|
||||
|
||||
> If you want to hide your users password, use the `secureTextEntry` prop.
|
||||
|
||||
## Communicating with Firebase
|
||||
|
||||
Now we've got our users input readily available in state, we can use the values to send to Firebase! First off we need a trigger to do this:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, TextInput, Button } from 'react-native';
|
||||
|
||||
...
|
||||
|
||||
_signIn = () => {
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder={'Email Address'}
|
||||
onChangeText={this._updateEmail}
|
||||
value={this.state.email}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder={'Password'}
|
||||
onChangeText={this._updatePassword}
|
||||
value={this.state.password}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={'Sign In'}
|
||||
onPress={this._signIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
As mentioned in 'Understanding Firebase Auth', we can call the `signInAndRetrieveDataWithEmailAndPassword` method on the Firebase API within our `_signIn` method,
|
||||
using the state values.
|
||||
|
||||
```js
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
...
|
||||
|
||||
_signIn = () => {
|
||||
// extract the values from state
|
||||
const { email, password } = this.state;
|
||||
|
||||
firebase.auth().signInAndRetrieveDataWithEmailAndPassword(email, password)
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The Firebase call will catch any errors (see list [here](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#signInAndRetrieveDataWithEmailAndPassword))
|
||||
which may occur, such as a bad email address or incorrect email/password combination.
|
||||
|
||||
You may notice we don't listen our for the success response from the call (via the `.then`). As you may remember, back in our `App` component
|
||||
our listener using `onAuthStateChanged` will pick up any successful sign in that occurs - and you guessed it, update the Redux store with
|
||||
our users details, which causes `App` to re-render with our new `AuthenticatedStack`!
|
||||
|
||||
The `Welcome` component implemented below on the `AuthenticatedStack` contains a button which calls `firebase.auth().signOut()`, which triggers the
|
||||
reverse motion of showing the `UnauthenticatedStack` to the user - simple!
|
||||
|
||||
![Sign In](assets/signin.gif =300x*)
|
||||
|
||||
> You may notice there's a delay when pressing the "Sign In" button and the `onAuthStateChanged` lister firing. An app should always give feedback
|
||||
to a user when an action is happening. This can be accomplished through state/redux using many of the React Native components available such as
|
||||
[`ActivityIndicator`](https://facebook.github.io/react-native/docs/activityindicator.html). You'll also want to handle any errors back from
|
||||
Firebase to show to your user!
|
||||
|
||||
## Register / Forgot Password
|
||||
|
||||
The logic for registering or submitting a forgot password request is exactly the same as our sign in logic. Simply follow the same pattern
|
||||
as we've done above using `TextInput` components and state. Once you've got the desired information, call Firebase with the methods you need, such as
|
||||
[`createUserAndRetrieveDataWithEmailAndPassword`](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#createUserAndRetrieveDataWithEmailAndPassword)
|
||||
or [`sendPasswordResetEmail`](https://rnfirebase.io/docs/v3.2.x/auth/reference/auth#sendPasswordResetEmail)!
|
||||
|
||||
Remember to allow the user to navigate between these screens using the `navigate` method provided by `react-navigation`.
|
||||
|
||||
> You could also try implementing [`TabNavigation`](https://reactnavigation.org/docs/tab-based-navigation.html) to allow horizontal navigation between
|
||||
screens which is another common navigation pattern.
|
|
@ -0,0 +1,117 @@
|
|||
# Facebook Login
|
||||
|
||||
Rather than users signing in to your app with an email and password, Firebase provides the opportunity to integrate with a number of login providers
|
||||
(or even your own!). It does this by creating a `credential` from an [OAuth](https://oauth.net/) request which your login provider returns, such as an
|
||||
`accessToken`.
|
||||
|
||||
The Firebase API allows us to call `signInAndRetrieveDataWithCredential` with a generated credential. You guessed it, just as we accomplished in
|
||||
'Creating a sign in form', the method triggers `onAuthStateChanged` if the request was a success - super simple!
|
||||
|
||||
## Installing `react-native-fbsdk`
|
||||
|
||||
Luckily as Facebook own React Native, they provide a handy wrapper around their own SDK to integrate with React Native, called [`react-native-fbsdk`](https://github.com/facebook/react-native-fbsdk).
|
||||
|
||||
```bash
|
||||
npm install --save react-native-fbsdk
|
||||
```
|
||||
|
||||
To save explaining how to install this library, refer to their [documentation](https://developers.facebook.com/docs/react-native) on how to
|
||||
install the library into your React Native project on both Android & iOS.
|
||||
|
||||
## Creating a credential
|
||||
|
||||
A credential can be generated by first obtaining an `accessToken` from Facebook. This is returned once a user successfully signs in via their
|
||||
Facebook app or popup (which the `react-native-fbsdk` handles).
|
||||
|
||||
In our `Login.js` component, go ahead and create a new method called `_facebookLogin`:
|
||||
|
||||
```js
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
import { AccessToken, LoginManager } from 'react-native-fbsdk'; // import AccessToken and LoginManager
|
||||
|
||||
...
|
||||
|
||||
_facebookLogin = async () => {
|
||||
try {
|
||||
const result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
|
||||
|
||||
if (result.isCancelled) {
|
||||
throw new Error('User cancelled request'); // Handle this however fits the flow of your app
|
||||
}
|
||||
|
||||
console.log(`Login success with permissions: ${result.grantedPermissions.toString()}`);
|
||||
|
||||
// get the access token
|
||||
const data = await AccessToken.getCurrentAccessToken();
|
||||
|
||||
if (!data) {
|
||||
throw new Error('Something went wrong obtaining the users access token'); // Handle this however fits the flow of your app
|
||||
}
|
||||
|
||||
// create a new firebase credential with the token
|
||||
const credential = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
|
||||
|
||||
// login with credential
|
||||
await firebase.auth().signInAndRetrieveDataWithCredential(credential);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
There's quite a few steps involved here, which require asynchronous calls to both Facebook and Firebase so it's important to give feedback to your
|
||||
user whilst this process is running. There's also a number of errors which can occur, caused by the user (such as rejecting the request) or
|
||||
Firebase not having the Facebook provider enabled, so ensure the error is shown back to the user.
|
||||
|
||||
> You may notice here we make use of [`async/await`](https://ponyfoo.com/articles/understanding-javascript-async-await). This allows us to keep our code
|
||||
feeling synchronous and handle any false positive errors we want to catch without worry about promise chaining.
|
||||
|
||||
### Triggering the method
|
||||
|
||||
Quite simply, just like the `_signIn` method we call the `_facebookLogin` method with a custom `Button`:
|
||||
|
||||
```jsx
|
||||
// src/screens/unauthenticated/Login.js
|
||||
|
||||
...
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder={'Email Address'}
|
||||
onChangeText={this._updateEmail}
|
||||
value={this.state.email}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder={'Password'}
|
||||
onChangeText={this._updatePassword}
|
||||
value={this.state.password}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={'Sign In'}
|
||||
onPress={this._signIn}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={'Sign In with Facebook'}
|
||||
onPress={this._facebookLogin}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Updating your Facebook app ID & secret
|
||||
|
||||
Back in step 'Understanding Firebase Authentication', we enabled Firebase as a login provider on Facebook, however may have entered dummy values.
|
||||
|
||||
The `react-native-fbsdk` requires you to assign an app ID to your install as mentioned in their [quickstart guide](https://developers.facebook.com/quickstarts/?platform=android).
|
||||
Ensure that the app you choose also has the credentials on the Firebase console, otherwise you'll get an error back from Firebase when attempting to
|
||||
sign in with the generated credential.
|
|
@ -0,0 +1,43 @@
|
|||
# Getting Started
|
||||
|
||||
Welcome to the 'Authentication with Firebase' Codorial, using [React Native](http://facebook.github.io/react-native/) and [react-native-firebase](https://rnfirebase.io).
|
||||
Over the Codorial we'll cover how to setup your application to require both email/password login and social login using Facebook,
|
||||
to handling the users authenticated state using the popular [redux](https://redux.js.org/introduction) library whilst also integrating routing
|
||||
using [react-navigation](https://reactnavigation.org/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This Codorial assumes you know the basics of the following topics:
|
||||
|
||||
- React JS.
|
||||
- ES6 JavaScript.
|
||||
- Starting an app using an emulator on Android/iOS.
|
||||
- Understand how to setup a new Firebase project.
|
||||
- Managing your project using Android Stuido and/or XCode.
|
||||
- Installation of the [react-native-firebase](https://rnfirebase.io) library (see "Creating a base project" below).
|
||||
|
||||
This Codorial was created with React Native version `0.53.0`.
|
||||
|
||||
## Creating a base project
|
||||
|
||||
This project will take a bare bones React Native setup and explain every step required to implement a solid authentication flow in your application.
|
||||
To start we need a base project to work from.
|
||||
|
||||
Both options below require you to setup a new Firebase project and add the configuration file to your project - check out the [documentation](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) on how to do that if needed.
|
||||
|
||||
### Option 1: Using `react-native init`
|
||||
|
||||
You can quickly create a base React Native project using `react-native init` by following the React Native [documentation](http://facebook.github.io/react-native/docs/getting-started.html).
|
||||
|
||||
> Ensure you follow the "Building Projects with Native Code" tab, as the project won't work using Expo due to requiring native modules.
|
||||
|
||||
Once installed, you need to install the [react-native-firebase](https://rnfirebase.io/docs/v3.2.x/installation/initial-setup) library. Ensure you've
|
||||
also installed the Authentication module on your platform ([Android](https://rnfirebase.io/docs/v3.2.x/auth/android) or [iOS](https://rnfirebase.io/docs/v3.2.x/auth/ios))!
|
||||
|
||||
### Option 2: Using [react-native-firebase-starter](https://github.com/invertase/react-native-firebase-starter)
|
||||
|
||||
A starter kit has been created to help you get up and running with minimal setup needed. If you're new to React Native this will be perfect starting point.
|
||||
|
||||
> Keep in mind every Firebase module is installed in this starter kit. You can refer to the react-native-firebase [documentation](https://rnfirebase.io/docs) if you want to remove
|
||||
any unwanted modules.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Google Login
|
||||
|
||||
Much like Facebook loginin, Firebase provides the ability to accept and sign in with a Google credential.
|
||||
|
||||
## Installing `react-native-google-signin`
|
||||
|
||||
To sign in with Google, we recommend using `react-native-google-signin`. This provides a native way of obtaining the users
|
||||
Google accounts and their required `accessToken`.
|
||||
|
||||
```
|
||||
npm install react-native-google-signin --save
|
||||
react-native link react-native-google-signin
|
||||
```
|
||||
|
||||
## Creating a credential
|
||||
|
||||
To generate a new credential for the user, simply call the asynchronous `signIn` method `react-native-google-signin` provides and create a new
|
||||
credential from the `firebase.auth.GoogleAuthProvider`:
|
||||
|
||||
|
||||
```js
|
||||
import { GoogleSignin } from 'react-native-google-signin';
|
||||
|
||||
// Calling this function will open Google for login.
|
||||
export const googleLogin = async () => {
|
||||
try {
|
||||
// Add any configuration settings here:
|
||||
await GoogleSignin.configure();
|
||||
|
||||
const data = await GoogleSignin.signIn();
|
||||
|
||||
// create a new firebase credential with the token
|
||||
const credential = firebase.auth.GoogleAuthProvider.credential(data.idToken, data.accessToken)
|
||||
|
||||
// login with credential
|
||||
const currentUser = await firebase.auth().signInAndRetrieveDataWithCredential(credential);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The flow here is exactly the same as the rest of our app; logging in with the newly created credential will trigger our `onAuthStateChanged` lister to fire with
|
||||
the new user, logging us into the application.
|
|
@ -0,0 +1,243 @@
|
|||
# Handling Authentication State
|
||||
|
||||
Now we've got a basic navigation stack in place along with Redux, we can combine the two together to handle the users authenticated state.
|
||||
|
||||
## Listen for authentication state changes
|
||||
|
||||
As mentioned in "Understanding Firebase Auth", we can listen for auth state changes via `onAuthStateChanged`. As our app will require authentication
|
||||
to view the main content, we can conditionally render the 'unauthenticated' `StackNavigator` if the user is signed out in our `src/App.js`. Lets go
|
||||
ahead and add the boilerplate code to get this in motion:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
import UnauthenticatedStack from './screens/unauthenticated';
|
||||
|
||||
class App extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Listen for user auth state changes
|
||||
firebase.auth().onAuthStateChanged((user) => {
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// Render a blank screen whilst we wait for Firebase.
|
||||
// The listener generally trigger immediately so it will be too fast for the user to see
|
||||
if (this.state.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <UnauthenticatedStack />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
## Updating Redux with the user state
|
||||
|
||||
Rather than passing our `user` into component `state`, we're going to add it into into Redux instead. Firebase does provide direct access to the user
|
||||
via `firebase.auth().currentUser`, however as our app complexity grows we may want to integrate parts of the users data (for example the `uid`) into
|
||||
other parts of our Redux store. By storing the user in Redux, it is guaranteed that the user details will keep in-sync throughout our Redux store.
|
||||
|
||||
### Dispatching Actions
|
||||
|
||||
Another common Redux concept is called 'Dispatching Actions'. An action is an event with a unique name, which our reducer can listen out for and react
|
||||
to the action. Every action requires a `type` property and can pass any additional data along which the reducer needs to handle the action. Lets go ahead
|
||||
and create an `actions.js` file, where we'll define our first action:
|
||||
|
||||
```js
|
||||
// src/actions.js
|
||||
|
||||
// define our action type as a exportable constant
|
||||
export const USER_STATE_CHANGED = 'USER_STATE_CHANGED';
|
||||
|
||||
// define our action function
|
||||
export function userStateChanged(user) {
|
||||
return {
|
||||
type: USER_STATE_CHANGED, // required
|
||||
user: user ? user.toJSON() : null, // the response from Firebase: if a user exists, pass the serialized data down, else send a null value.
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To dispatch this action we need to again make use of `react-redux`. As our `App.js` has been provided the Redux store via the `Provider`
|
||||
component within `index.js`, we can use a [higher order component (HOC)](https://reactjs.org/docs/higher-order-components.html) called `connect` to provide the component with access to Redux:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
...
|
||||
|
||||
export default connect()(App);
|
||||
```
|
||||
|
||||
The `connect` HOC clones the given component with a function prop called `dispatch`. The `dispatch` function then takes an action, which when called 'dispatches'
|
||||
it to Redux. Lets jump back into our `App.js` and dispatch our action when `onAuthStateChanged` is triggered:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
// import our userStateChanged action
|
||||
import { userStateChanged } from './actions';
|
||||
|
||||
...
|
||||
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged((user) => {
|
||||
|
||||
// dispatch the imported action using the dispatch prop:
|
||||
this.props.dispatch(userStateChanged(user));
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
> You may want to consider implementing [`mapDispatchToProps`](https://github.com/reactjs/react-redux/blob/master/docs/api.md) to keep the action
|
||||
usage reusable & cleaner.
|
||||
|
||||
Now every time `onAuthStateChanged` is triggered by Firebase, our Redux action will be dispatched regardless of whether a user is signed in our out!
|
||||
|
||||
### Reducing state
|
||||
|
||||
Back on step 'Integrating Redux' we setup a very basic Redux store. In order for us to latch onto the dispatched action we need to listen out
|
||||
for the events being sent to the reducer. To do this we import the action type which we exported within `actions.js` and conditionally
|
||||
return new state when that action is dispatched:
|
||||
|
||||
```jsx
|
||||
// src/store.js
|
||||
|
||||
import { createStore } from 'redux';
|
||||
|
||||
// import the action type
|
||||
import { USER_STATE_CHANGED } from './actions';
|
||||
|
||||
function reducer(state = {}, action) {
|
||||
|
||||
// When USER_STATE_CHANGED is dispatched, update the store with new state
|
||||
if (action.type === USER_STATE_CHANGED) {
|
||||
return {
|
||||
user: action.user,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export default createStore(reducer);
|
||||
```
|
||||
|
||||
You may notice here that we return a brand new object rather than modifying the existing state. This is because Redux state is
|
||||
[immutable](https://facebook.github.io/immutable-js/). In order for Redux to know whether state has actually changed, it needs to compare the
|
||||
previous state with a new one.
|
||||
|
||||
> As your Redux state grows in complexity, it may be worth breaking your store out into multiple reducers. This can easily be achieved using
|
||||
[combineReducers](https://redux.js.org/api-reference/combinereducers) from the `redux` package.
|
||||
|
||||
### Subscribing to Redux state
|
||||
|
||||
Now our action is updating the store whenever it's dispatched, we can subscribe to specific parts of the data which we need in our React
|
||||
components. The power of using `react-redux` is that it allows us to subscribe to data within our store and update the component whenever that
|
||||
data changes - we do this via a function known as `mapStateToProps`. This function is passed as the first argument of our `connect` HOC and gets
|
||||
given the current Redux state. It returns an object, which is cloned as props into our component. Here's how it works:
|
||||
|
||||
```js
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isUserAuthenticated: !!state.user,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(App);
|
||||
```
|
||||
|
||||
With this code, our `App` component will receive a prop called `isUserAuthenticated`, which in our case will be a `true` or `false` value based on
|
||||
whether the `state.user` object exists or not. Every time Redux state changes, this logic is run. What's handy is that if the result of any
|
||||
prop has changed, the component will be updated with the new data. If none of the props-to-be have changed, the component doesn't update.
|
||||
|
||||
> Keep in mind that if you return a complex `Array` or `object`, `react-redux` will only shallow compare them. Even if your state does not change
|
||||
the component will still be re-rendered with the same data which can cause performance issues in our app if not handled. Therefore it is wise
|
||||
to break components out to only subscribe to specific parts of primitive state (such as `strings`, `booleans` etc).
|
||||
|
||||
As our `App` component contains our routes, any change in the `isUserAuthenticated` value will cause the entire app to re-render - which in this case
|
||||
is fine as we're conditionally changing navigation stacks. Lets implement that logic:
|
||||
|
||||
```jsx
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import firebase from 'react-native-firebase';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UnauthenticatedStack from './screens/unauthenticated';
|
||||
import AuthenticatedStack from './screens/authenticated';
|
||||
import { userStateChanged } from './actions';
|
||||
|
||||
class App extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged((user) => {
|
||||
|
||||
this.props.dispatch(userStateChanged(user));
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// Render a blank screen whilst we wait for Firebase.
|
||||
// The listener generally trigger immediately so it will be too fast for the user to see
|
||||
if (this.state.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.props.isUserAuthenticated) {
|
||||
return <UnauthenticatedStack />;
|
||||
}
|
||||
|
||||
return <AuthenticatedStack />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isUserAuthenticated: !!state.user,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(App);
|
||||
```
|
||||
|
||||
As you can see in our `render` method, if the `isUserAuthenticated` value is `false`, we render our `UnauthenticatedStack`. If it's `true` we can
|
||||
render a new stack, in this case called `AuthenticatedStack` which is waiting for you to setup!
|
|
@ -0,0 +1,86 @@
|
|||
# Integrating Redux
|
||||
|
||||
Redux has become somewhat of a buzz word in the React community, and is generally used in most projects without thought. This Codorial
|
||||
won't go into details on what it is as their own [documentation](https://redux.js.org/introduction/motivation) does a wonderful job at explaining
|
||||
what it's for and why to use it.
|
||||
|
||||
*TLDR;* Redux provides your app with a single "state" (data), which can be accessed by any component. You can subscribe to this data to cause
|
||||
a component update whenever something changes, even if it's deeply nested.
|
||||
|
||||
Although the end product of this Codorial certainly doesn't require Redux to function, as your app grows in complexity Redux becomes more and
|
||||
more important to manage your data.
|
||||
|
||||
## Installing Redux
|
||||
|
||||
Lets go ahead by installing the core Redux library and the React bindings:
|
||||
|
||||
```bash
|
||||
npm install --save redux react-redux
|
||||
```
|
||||
|
||||
Now within our projects `src` directory, create a `store.js` file. This file will contain all of our Redux logic, however you may want to break
|
||||
this out into multiple directories as your projects grows in complexity.
|
||||
|
||||
```js
|
||||
// src/store.js
|
||||
import { createStore } from 'redux';
|
||||
|
||||
// Create a reducer with empty state (see below for explanation)
|
||||
function reducer(state = {}, action) {
|
||||
return state;
|
||||
}
|
||||
|
||||
export default createStore(reducer);
|
||||
```
|
||||
|
||||
By default state is `null`, however we're setting it to an empty `object` (`state = {}`) so we can attempt to access
|
||||
shallow nested properties even if they don't exist.
|
||||
|
||||
> You may want to consider installing the [redux-logger](https://github.com/evgenyrodionov/redux-logger) library to improve
|
||||
your Redux experience.
|
||||
|
||||
### Reducer
|
||||
|
||||
A reducer is a simple JavaScript function which takes two arguments: `state` & `action`. The idea of a reducer is to take "some data" from an `action`
|
||||
and return new state.
|
||||
|
||||
- `state` is any sort of data, which cannot be altered (immutable). A reducer must return a new value each time. More on this later.
|
||||
- `action` is an object containing a `type`, and any unreduced data. More on this later.
|
||||
|
||||
## Integrating Redux into the app
|
||||
|
||||
Our Redux store is now ready to be used. `react-redux` provides us with a `Provider` component which "provides" any children
|
||||
with access to the store via [context](https://reactjs.org/docs/context.html). Luckily we don't need to worry about this too much as the library
|
||||
takes care of the hard work!
|
||||
|
||||
Back within our original bootstrap file, we'll wrap the `App` component in the `Provider` component, so our business logic has access to Redux.
|
||||
|
||||
```jsx
|
||||
// src/index.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux'; // Import the Provider component
|
||||
|
||||
import App from './App';
|
||||
import store from './store';
|
||||
|
||||
function bootstrap() {
|
||||
|
||||
// Init any external libraries here!
|
||||
|
||||
return class extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default bootstrap;
|
||||
```
|
||||
|
||||
Although noting will visually change, our app now has access to the power of Redux!
|
|
@ -0,0 +1,125 @@
|
|||
# Project Structure
|
||||
|
||||
Although it may seem trivial, having a good initial project structure ensures your code will be clean and reusable.
|
||||
The following step gives an opinionated guide to how this might look, which will work across both Android & iOS.
|
||||
|
||||
## Entry file
|
||||
|
||||
Every fresh React Native project a key file, an `index.js`, which currently renders a simple React component
|
||||
with basic styling. Rather than keeping our business logic within this file, we're going to keep it contained in it's own
|
||||
directory.
|
||||
|
||||
We'll achieve this by creating a `src` directory where our own code for the app will live. Create the directory with an `index.js` file, so your
|
||||
project structure resembles the following:
|
||||
|
||||
```
|
||||
- node_modules/
|
||||
- android/
|
||||
- ios/
|
||||
- src/
|
||||
-- index.js
|
||||
- index.js
|
||||
```
|
||||
|
||||
Now we can reference our bootstrap file in the `index.js` file, so both our platform share the same entry point:
|
||||
|
||||
```js
|
||||
// index.js
|
||||
|
||||
import { AppRegistry } from 'react-native';
|
||||
import bootstrap from './src';
|
||||
|
||||
AppRegistry.registerComponent('RNFirebaseStarter', () => bootstrap());
|
||||
```
|
||||
|
||||
## Bootstrapping your project
|
||||
|
||||
You may have noticed before, but the `bootstrap` import is a function. This allows us to setup or initialize any external modules before our
|
||||
React based application kick starts (such as [react-native-i18n](https://github.com/AlexanderZaytsev/react-native-i18n)).
|
||||
|
||||
Lets go ahead and setup our bootstrap file:
|
||||
|
||||
```js
|
||||
// src/index.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
function bootstrap() {
|
||||
|
||||
// Init any external libraries here!
|
||||
|
||||
return class extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
Bootstrapped!
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default bootstrap;
|
||||
```
|
||||
|
||||
Although this function simply returns a basic React component, later we'll be able to see the power of having a bootstrap file which
|
||||
consumes our entire application.
|
||||
|
||||
Go ahead and boot up your app onto your emulator. You should simply be presented with a plain screen with the words "Bootstrapped!".
|
||||
|
||||
![Bootstrapped!](assets/app-bootstrapped.jpg =300x*)
|
||||
|
||||
Although a good starting point, we want to separate we'll our business logic out of the bootstrap file, keeping it purely for app
|
||||
initialization purposes. This can simply be done by creating a basic React component called `App.js`, which will also live in the `src` directory;
|
||||
|
||||
```js
|
||||
// src/App.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
Bootstrapped!
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
Now we can reference this component within our bootstrap setup and return it from the bootstrap component:
|
||||
|
||||
```js
|
||||
// src/index.js
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import App from './App';
|
||||
|
||||
function bootstrap() {
|
||||
|
||||
// Init any external libraries here!
|
||||
|
||||
return class extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<App />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default bootstrap;
|
||||
```
|
|
@ -0,0 +1,197 @@
|
|||
# Understanding Firebase Authentication
|
||||
|
||||
Before we dive into the logic of implementing authentication, it's first important to understand the Firebase API, and how it handles authentication
|
||||
with the various options we have.
|
||||
|
||||
As we're also working in React, we'll cover how Firebase's asynchronous API fits in with Reacts lifecycle methods.
|
||||
Luckily [react-native-firebase](https://rnfirebase.io) follows the Firebase web SDK API making this a breeze!
|
||||
|
||||
## Enabling authentication
|
||||
|
||||
We need to tell Firebase that we plan on using authentication and also enable a couple of the many login providers
|
||||
which Firebase supports. Head over to the [Firebase console](https://console.firebase.google.com/u/0/) and select the project you're using.
|
||||
|
||||
Find the Authentication section and you'll be prompted with a number of options. To get started, we want to select the "SIGN-IN METHOD" tab.
|
||||
|
||||
You'll see we have a number of options here, however for the purposes of this Codorial we'll be using "Email/Password" and "Facebook" as our providers.
|
||||
Go ahead and enable these:
|
||||
|
||||
![Enabled Providers](assets/auth-providers.jpg)
|
||||
|
||||
> If you don't have a Facebook app, simply enter dummy values. We'll cover this later on.
|
||||
|
||||
## Listening to the users authentication state
|
||||
|
||||
The Firebase API provides a simple yet powerful listener, which triggers when some event changes with the user.
|
||||
This can be as obvious the user signing out or as subtle as the user validating their email address. Whatever the event, it triggers the same method: `onAuthStateChanged`.
|
||||
|
||||
```js
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
firebase.auth().onAuthStateChanged((user) => {
|
||||
console.log(user);
|
||||
});
|
||||
```
|
||||
|
||||
The callback for the `onAuthStateChanged` method returns a single parameter, commonly referred to as `user`.
|
||||
|
||||
The concept here is simple;
|
||||
|
||||
- the method is first called once Firebase responds, then any time user state changes thereafter.
|
||||
- if a user is "signed in", our parameter will be a [`User`](https://firebase.google.com/docs/reference/js/firebase.User) `class`, containing all sorts of information we know about the user,
|
||||
from their e-mail address to any social provider IDs they may have signed in through.
|
||||
- if the user signed out, the parameter will be `null` value.
|
||||
|
||||
> The `user` class provides a `.toJSON()` method to serialize the users details if required.
|
||||
|
||||
### Handling authentication state when the app closes
|
||||
|
||||
A common question we get is how to handle the users authenticated state when the app closes/restarts so they don't have to keep logging in each
|
||||
time they open the app. Luckily this is all handled through Firebase so you don't have to worry about a thing - they'll only be signed out if they
|
||||
choose to, or the app is uninstalled.
|
||||
|
||||
## Creating a new account
|
||||
|
||||
Creating a new account on Firebase is very easy. Another method called `createUserAndRetrieveDataWithEmailAndPassword` is available which does exactly what it
|
||||
says on the tin! This is an asynchronous promise which will throw an exception if something is wrong (such as email taken, or password too short).
|
||||
Creating a user will also sign them in at the same time.
|
||||
|
||||
```js
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
firebase.auth().createUserAndRetrieveDataWithEmailAndPassword('jim.bob@gmail.com', 'supersecret!')
|
||||
.then((user) => {
|
||||
console.log('New User', user);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Woops, something went wrong!', error);
|
||||
});
|
||||
```
|
||||
|
||||
What's great about this is we don't need to know about the user within the `.then`, as any `onAuthStateChanged` listener would get triggered with our new
|
||||
users details - how awesome is that.
|
||||
|
||||
## Signing into an existing account
|
||||
|
||||
Unsurprisingly, Firebase offers a method called `signInAndRetrieveDataWithEmailAndPassword`, which follows the exact same flow as `createUserAndRetrieveDataWithEmailAndPassword`:
|
||||
|
||||
```js
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
firebase.auth().signInAndRetrieveDataWithEmailAndPassword('jim.bob@gmail.com', 'supersecret!')
|
||||
.then((user) => {
|
||||
console.log('Existing User', user);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Woops, something went wrong!', error);
|
||||
});
|
||||
```
|
||||
|
||||
## Using with React
|
||||
|
||||
Firebase on it's own is super simple, however when using in a React environment there's some gotchas you need to be mindful of.
|
||||
|
||||
### Handling state changes
|
||||
|
||||
For any React component to update, a state or prop change needs to occur. As our Firebase auth methods are asynchronous we cannot rely on
|
||||
the data being available on component mount. To solve this issue, we can make use of state:
|
||||
|
||||
```jsx
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
loading: false,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
firebase.auth().onAuthStateChanged((user) => {
|
||||
if (user) {
|
||||
this.setState({
|
||||
user: user.toJSON(), // serialize the user class
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, user } = this.state;
|
||||
|
||||
// Firebase hasn't responded yet
|
||||
if (loading) return null;
|
||||
|
||||
// Firebase has responded, but no user exists
|
||||
if (!user) {
|
||||
return (
|
||||
<View>
|
||||
<Text>Not signed in</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Firebase has responded, and a user exists
|
||||
return (
|
||||
<View>
|
||||
<Text>User signed in! {user.email}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribing/Un-subscribing from listeners
|
||||
|
||||
When subscribing to a new listener, such as `onAuthStateChanged`, a new reference to it is made in memory which has no knowledge of the
|
||||
React environment. If a component within your app mounts and subscribes, the method will still trigger even if your component unmounted.
|
||||
If this happens and you're updating state, you'll get a yellow box warning.
|
||||
|
||||
To get around this, Firebase returns an unsubscribe function to every subscriber method, which when calls removes the subscription from memory.
|
||||
This can be easily implemented using React lifecycle methods and class properties:
|
||||
|
||||
```jsx
|
||||
import React, { Component } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import firebase from 'react-native-firebase';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.unsubscribe = null; // Set a empty class method
|
||||
this.state = {
|
||||
loading: true,
|
||||
user: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Assign the class method to the unsubscriber response
|
||||
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
|
||||
// handle state changes
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Call the unsubscriber if it has been set
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Further reading
|
||||
|
||||
The above examples just scratch the surface of whats available with Firebase auth. Firebase itself provides some in-depth documentation
|
||||
on authentication and the many different implementation paths you can follow.
|