¶Membuat Custom Context Authentikasi React Native
Pada artikel kali ini kita akan membuat sebuah context untuk autentikasi user, tapi sebelum itu kita akan mendefinisikan type data dari User. silahkan teman-teman buat folder baru dengan nama types, kemudian buat file baru dengan nama user.ts, kemudian masukan kode berikut ini.
export interface User {
id: number;
name: string;
email: string;
}
Pada kode di atas, kita mendefinisikan sebuah interface User yang merepresentasikan struktur data untuk sebuah data User.
export interface User {
id: number;
name: string;
email: string;
}
id: number;→ Kolomidkita set dengan tipe datanumber.name: string;→ Kolomnamekita set dengan tipe datastring.email: string;→ Kolomemailkita set dengan tipe datastring.
Setelah berhasil membuat context user disini kita akan lanjutkan untuk pembuatan sebuah context authentikasi user. Silahkan teman-teman buat folder baru dengan nama context, kemudian di dalam folder tersebut buat file baru dengan nama AuthContext.tsx. Kemudian masukkan kode berikut ini:
import api from "@/api";
import { User } from "@/types/user";
import AsyncStorage from '@react-native-async-storage/async-storage';
import { router } from "expo-router";
import { createContext, useEffect, useState } from "react";
// define type auth context
type AuthContextType = {
user: User | null;
isLoading: boolean;
fetchUser: () => Promise<void>;
handleRegister: (name: string, email: string, password: string) => Promise<void>;
handleLogin: (email: string, password: string) => Promise<void>;
handleLogout: () => Promise<void>;
};
// define context object
export const AuthContext = createContext<AuthContextType>({
user: null,
isLoading: false,
fetchUser: async () => {},
handleRegister: async () => {},
handleLogin: async () => {},
handleLogout: async () => {},
})
export const AuthProvider = ({ children } : {children: React.ReactNode}) => {
// define state user
const [user, setUser] = useState<User | null>(null);
// define state loading
const [isLoading, setIsLoading] = useState(true);
// define method fetchUser
const fetchUser = async () => {
try{
// hit api profile
const response = await api.get('/profile');
// update state user with response api
setUser(response.data.user);
}catch{
// update state user to null
setUser(null);
}finally{
// update state is loading to false
setIsLoading(false);
}
}
// define method register
const handleRegister = async (name: string, email: string, password: string) => {
// hit api register
const response = await api.post('/register', { name, email, password});
// get token user
const token = response.data.access_token;
// save token
await AsyncStorage.setItem('access_token', token);
// set token to headers authorization
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// call method fetch user
await fetchUser();
// push to dashboard screen
router.replace('/(home)/dashboard')
}
// define method login
const handleLogin = async (email: string, password: string) => {
try{
// hit api login
const response = await api.post('/login', {email, password})
// get token user
const token = response.data.access_token;
// save token
await AsyncStorage.setItem('access_token', token);
// set token to headers authorization
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// call method fetch user
await fetchUser();
// push to dashboard screen
router.replace('/(home)/dashboard')
}catch(error){
throw error;
}
}
// define method logout
const handleLogout = async () => {
// hit api logout
await api.post('/logout');
// remove token user
await AsyncStorage.removeItem('access_token');
// update state user to null
setUser(null);
// push to login screen
router.replace('/(auth)/login')
}
// call method fetch user using useEffect hooks
useEffect(() => {
// define method to check authentication
const checkAuth = async () => {
try{
// get access_token user
const token = await AsyncStorage.getItem('access_token');
if(token){
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
await fetchUser();
}else{
setIsLoading(false);
}
}catch{
setIsLoading(false);
}
}
checkAuth();
}, [])
return (
<AuthContext.Provider value={{ fetchUser, user, isLoading, handleRegister, handleLogin, handleLogout }}>
{children}
</AuthContext.Provider>
);
}
Pertama-tama, kita melakukan import beberapa module yang kita butuhkan, seperti:
api: konfigurasi axios untuk mengakses API kita.User: tipe data user.AsyncStorage: digunakan untuk menyimpan token autentikasi secara lokal.router: untuk melakukan navigasi antar halaman menggunakanexpo-router.- Beberapa hooks bawaan dari React seperti
createContext,useEffect, danuseState.
¶Mendefinisikan Tipe Context
Selanjutnya kita mendefinisikan struktur tipe data yang akan digunakan oleh AuthContext.
type AuthContextType = {
user: User | null;
isLoading: boolean;
fetchUser: () => Promise<void>;
handleRegister: (name: string, email: string, password: string) => Promise<void>;
handleLogin: (email: string, password: string) => Promise<void>;
handleLogout: () => Promise<void>;
};
Pada kode diatas, kita mendefinisikan beberapa tipe data context yang akan menyimpan data user, status isLoading, serta beberapa method yang akan kita buat didalam context seperti fetchUser, handleRegister, handleLogin dan handleLogout.
¶Membuat dan Mengekspor Context
Lalu kita buat context-nya menggunakan createContext, dan langsung beri nilai default agar tidak terjadi error saat digunakan sebelum AuthProvider terpasang.
export const AuthContext = createContext<AuthContextType>({
user: null,
isLoading: false,
fetchUser: async () => {},
handleRegister: async () => {},
handleLogin: async () => {},
handleLogout: async () => {},
});
¶Membuat Komponen AuthProvider
Disini kita akan membuat sebuah komponen baru bernama AuthProvider yang akan digunakan untuk membungkus seluruh aplikasi kita. Komponen ini akan menyediakan data user dan fungsi autentikasi ke seluruh component anak di dalamnya.
export const AuthProvider = ({ children } : {children: React.ReactNode}) => {
¶State User dan Loading
Dalam AuthProvider, kita mendefinisikan dua state:
user: menyimpan data user yang sedang login.isLoading: penanda bahwa proses autentikasi sedang berlangsung.
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
¶Method fetchUser
Method ini digunakan untuk mengambil data profil user yang sedang login.
const fetchUser = async () => {
try {
const response = await api.get('/profile');
setUser(response.data.user);
} catch {
setUser(null);
} finally {
setIsLoading(false);
}
}
Jika API berhasil diakses, maka data user akan disimpan. Jika gagal (misalnya token tidak valid), maka user akan diset ke null. Terakhir, kita pastikan isLoading akan di-set false.
¶Method handleRegister
Digunakan untuk mendaftarkan user baru.
const handleRegister = async (name: string, email: string, password: string) => {
const response = await api.post('/register', { name, email, password });
const token = response.data.access_token;
await AsyncStorage.setItem('access_token', token);
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
await fetchUser();
router.replace('/(home)/dashboard');
}
Setelah berhasil register:
- Token disimpan ke
AsyncStorage - Token diset ke header
Authorization - Data user diambil ulang menggunakan
fetchUser - User diarahkan ke halaman dashboard
¶Method handleLogin
Digunakan untuk login user yang sudah terdaftar.
const handleLogin = async (email: string, password: string) => {
try {
const response = await api.post('/login', { email, password });
const token = response.data.access_token;
await AsyncStorage.setItem('access_token', token);
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
await fetchUser();
router.replace('/(home)/dashboard');
} catch (error) {
throw error;
}
}
Alurnya hampir sama seperti handleRegister, namun di sini kita menangani error yang dilempar dari API jika login gagal.
¶Method handleLogout
Method ini digunakan untuk logout user yang sedang aktif.
const handleLogout = async () => {
await api.post('/logout');
await AsyncStorage.removeItem('access_token');
setUser(null);
router.replace('/(auth)/login');
}
Saat logout:
- Token dihapus dari penyimpanan lokal
- User dihapus dari state
- Aplikasi diarahkan kembali ke halaman login
¶Mengecek Autentikasi Saat Aplikasi Dimuat
Kita ingin memastikan bahwa jika user memiliki token tersimpan, maka data user akan langsung dimuat saat aplikasi dibuka, disini kita menggunakan hooks useEffect.
useEffect(() => {
const checkAuth = async () => {
try {
const token = await AsyncStorage.getItem('access_token');
if (token) {
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
await fetchUser();
} else {
setIsLoading(false);
}
} catch {
setIsLoading(false);
}
}
checkAuth();
}, []);
Terakhir, kita kembalikan provider dan berikan semua method dan data yang sudah kita buat.
return (
<AuthContext.Provider
value={{ fetchUser, user, isLoading, handleRegister, handleLogin, handleLogout }}
>
{children}
</AuthContext.Provider>
);
¶Membuat Custom Hook untuk Mengakses Context
Agar kita lebih mudah dalam menggunakan context autentikasi yang kita buat sebelumnya di seluruh komponen aplikasi, kita bisa membuat sebuah custom hook bernama useAuth. Silakan teman-teman buat folder baru dengan nama hooks, lalu di dalam folder tersebut buat file baru dengan nama useAuth.ts, dan masukkan kode berikut:
import { AuthContext } from '@/context/AuthContext';
import { useContext } from 'react';
export const useAuth = () => useContext(AuthContext);
Pada kode di atas :
- Kita mengimpor
AuthContextdari fileAuthContext.tsxyang sebelumnya telah kita buat. - Kita juga menggunakan
useContextdari React, yang berfungsi untuk mengakses isi dari context.
import { AuthContext } from '@/context/AuthContext';
import { useContext } from 'react';
Kemudian kita membuat sebuah fungsi useAuth, yaitu custom hook yang akan mengembalikan isi dari AuthContext.
export const useAuth = () => useContext(AuthContext);
Dengan adanya custom hook ini, kita tidak perlu lagi mengimpor dan menggunakan useContext(AuthContext) secara manual di setiap komponen. Cukup panggil useAuth() saja, dan kita sudah bisa mengakses:
userisLoadinghandleLogin()handleRegister()handleLogout()fetchUser()