Building A React Native JWT Client - API Requests and AsyncStorage
Building A React Native JWT Client - API Requests and AsyncStorage
In Part II, we established our React Native app and its base
components/screens. (Github Repo)
In this third and final part, we will use Axios to make HTTP requests to
our Elixir API, and we will save relevant data to our device using React
Native’s AsyncStorage module.
While this is Part III of the Elixir/Phoenix — React Native JSON Web Token
guide, a React Native JWT client built with this guide will work with any
matching API.
While we could use JavaScript’s fetch() method for our API requests,
we are going to use the Axios HTTP request library instead.
Axios will also catch errors properly when we receive error statuses,
whereas fetch() will return ok when it receives a error statuses (such
as 500), which would be problematic.
Let’s install axios in our app’s root directory, with yarn add axios , or
npm i --save axios if you do not have yarn installed.
1 # snip
2
3 registerUser() {
4 const { email, password, password_confirmation } = this
5
6 this.setState({ error: '', loading: true });
7
8 // NOTE HTTP is insecure, only post to HTTPS in product
9
10 axios.post("http://localhost:4000/api/v1/sign_up",{
11 user: {
12 email: email,
13 password: password,
14 password_confirmation: password_confirmation
15 }
16 },)
17 then((response) => {
1 # snip
2 const deviceStorage = {
3
4 async saveItem(key, value) {
5 try {
6 await AsyncStorage.setItem(key, value);
7 } catch (error) {
8 console.log('AsyncStorage Error: ' + error.message);
9 }
The try{ await function() } clause above makes your app wait until
the function contained is completed before executing any successive
functions. (Make it to the end of this guide for a bonus note on
Asynchronous vs Synchronous functions 😉.)
axios.post("http://localhost:4000/api/v1/sign_up",{
user: {
email: email,
password: password,
password_confirmation: password_confirmation
}
},)
.then((response) => {
console.log(response);
Now, open up the remote debugger console and take a look at our
response object:
Our JWT is located at .data.jwt on our response object, so
response.data.jwt.
Let’s remove our console.log and pass the JWT part of our response
through deviceStorage.saveKey:
axios.post("http://localhost:4000/api/v1/sign_up",{
user: {
email: email,
password: password,
password_confirmation: password_confirmation
}
},)
.then((response) => {
deviceStorage.saveKey("id_token", response.data.jwt);
})
Now, we’re saving our response JWT to local storage, but we aren’t
saving it to our root component state. Let’s do that.
1 # snip
2
3 export default class App extends Component {
4 constructor() {
5 super();
6 this.state = {
7 jwt: '',
8 loading: true
9 }
10
11 this.newJWT = this.newJWT.bind(this);
12 }
13
14 newJWT(jwt){
Still in App.js, pass newJWT(JWT) through <Auth> as a prop:
newJWT(jwt){
this.setState({
jwt: jwt
});
}
render() {
if (!this.state.jwt) {
return (
<Auth newJWT={this.newJWT}/>
);
1 # snip
2
3 whichForm() {
4 if(!this.state.showLogin){
5 return(
6 <Registration newJWT={this.props.newJWT} authSwitch
7 );
8 } else {
9 return(
10 <Login newJWT={this.props.newJWT} authSwitch={this.
11 )
1 # snip
2
3 axios.post("http://localhost:4000/api/v1/sign_up",{
4 user: {
5 email: email,
6 password: password,
7 password_confirmation: password_confirmation
8 }
9 },)
10 .then((response) => {
11 d i St K ("id t k " d t j t)
Now, our axios.post() saves the returned JWT to device storage and
sets our JWT to our parent app state.
1 #snip
2
3 class Registration extends Component {
4 constructor(props){
5 # code omitted
6
7 this.registerUser = this.registerUser.bind(this);
8 this.onRegistrationFail = this.onRegistrationFail.bind(
9 }
10
11 registerUser() {
12 # snip
13
14 axios.post("http://localhost:4000/api/v1/sign_up",{
15 # code omitted
16
17 .catch((error) => {
18 console.log(error);
19 this.onRegistrationFail();
20 });
When our error state is set to 'Registration Failed' , the error will be
rendered in the form error text we wrote earlier:
<Text style={errorTextStyle}>
{error}
</Text>
1 # snip
2 import deviceStorage from './services/deviceStorage.js';
3
4 export default class App extends Component {
5 constructor() {
6 super();
7 this.state = {
8 jwt: '',
9 }
10
11 this.newJWT = this.newJWT.bind(this);
12 this.deleteJWT = deviceStorage.deleteJWT.bind(this);
Now, add loading: true to our initial state and run this.loadJWT()
Now, if you run this app in simulator, the LoggedIn screen will load if
your device contains a JWT!
Also, if you press the Log Out button while logged in, that JWT will be
deleted and you will be sent back to the Auth screen!
const styles = {
container: {
flex: 1,
justifyContent: 'center'
},
emailText: {
alignSelf: 'center',
color: 'black',
fontSize: 20
},
errorText: {
alignSelf: 'center',
fontSize: 18,
color: 'red'
}
};
render() {
const { container, emailText, errorText } = styles;
const { loading, email, error } = this.state;
false :
if (loading){
return(
<View style={container}>
<Loading size={'large'} />
</View>
)
} else {
return(
<View style={container}>
<View>
{email ?
<Text style={emailText}>
Your email: {email}
</Text>
:
<Text style={errorText}>
{error}
</Text>}
</View>
<Button onPress={this.props.deleteJWT}>
Log Out
</Button>
</View>
);
}
render() {
if (this.state.loading) {
# code omitted
} else if (!this.state.jwt) {
# code omitted
} else if (this.state.jwt) {
return (
<LoggedIn jwt={this.state.jwt} deleteJWT=
{this.deleteJWT} />
);
}
}
Now, go back to LoggedIn.js.
componentDidMount(){
const headers = {
'Authorization': 'Bearer ' + this.props.jwt
};
}
componentDidMount(){
const headers = {
'Authorization': 'Bearer ' + this.props.jwt
};
axios({
method: 'GET',
url: 'http://localhost:4000/api/v1/my_user',
headers: headers,
})
}
We pass the request type ( GET , if you didn’t know by now) through
method: , our API endpoint through url: , and our JWT
‘Authorization’ headers const through headers: .
componentDidMount(){
const headers = {
'Authorization': 'Bearer ' + this.props.jwt
};
axios({
method: 'GET',
url: 'http://localhost:4000/api/v1/my_user',
headers: headers,
}).then((response) => {
this.setState({
email: response.data.email,
loading: false
});
}).catch((error) => {
this.setState({
error: 'Error retrieving data',
loading: false
});
});
}
Now, let’s run our Phoenix server from Part I with mix phx.server .
In a new CLI window, run our React Native app with react-native
Wrapping Up
Congratulations if you made it this far! I hope you got as much out of
reading this guide as I did out of writing it.
And, as always…
🍹Tips Appreciated! 😉
My Bitcoin address: 1QJuBzHpis4jqQXnSuYxKzGS4Yu3GHhNtX
In almost every case, the console.log() above will fail to display an item
retrieved from AsyncStorage because the console.log() will run
before getItem(“some_key”) finishes running.