Creating a complete authentication flow using context-api. (Step-by-step)
This flow can be replicated to React-JS (Web)
npx react-native init reactNativeAuth --template react-native-template-typescript
yarn ios
Note: If the Metro Bundler not open automaticly, enter inside directory and run: yarn start
- delete App.tsx from root project.
- create a new folder:
src
- create a
App.tsx
inside src
import React from 'react';
import { View } from 'react-native';
// FC = Function Component
const App: React.FC = () => {
return <View />;
}
export default App;
...
import App from './src/App';
...
yarn add @react-navigation/native
Depencies Instructions on: https://reactnavigation.org/docs/getting-started
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
npx pod-install ios
Include on the first line off App.tsx
import 'react-native-gesture-handler';
Wrap the whole app in NavigationContainer. Usually you'd do this in your entry file, such as index.js or App.js:
...
import { NavigationContainer } from '@react-navigation/native';
const App: React.FC = () => {
return (
<NavigationContainer>
<View />
</NavigationContainer>
);
};
Run yarn ios
again to restart the emulator because components of the native library have been installed.
yarn add @react-navigation/stack
mkdir src/pages
mkdir src/pages/Dashboard
touch src/pages/Dashboard/index.tsx
mkdir src/pages/SignIn
touch src/pages/SignIn/index.tsx
mkdir src/routes
touch src/routes/app.routes.tsx
touch src/routes/auth.routes.tsx
import React from 'react';
import {View} from 'react-native';
const Dashboard: React.FC = () => <View />;
export default Dashboard;
import React from 'react';
import {View} from 'react-native';
const SignIn: React.FC = () => <View />;
export default SignIn;
import React from 'react';
import Dashboard from '../pages/Dashboard/index';
import { createStackNavigator } from '@react-navigation/stack';
const AppStack = createStackNavigator();
const AppRoutes: React.FC = () => (
<AppStack.Navigator>
<AppStack.Screen name="Dashboard" component={Dashboard} />
</AppStack.Navigator>
);
export default AppRoutes;
Note: Routes for user authentication.
import React from 'react';
import SignIn from '../pages/SignIn/index';
import { createStackNavigator } from '@react-navigation/stack';
const AuthStack = createStackNavigator();
const AuthRoutes: React.FC = () => (
<AuthStack.Navigator>
<AuthStack.Screen name="SignIn" component={SignIn} />
</AuthStack.Navigator>
);
export default AuthRoutes;
mkdir src/services
touch src/services/auth.tsx
touch src/routes/index.tsx
interface Response {
token: string;
user: {
name: string;
email: string;
}
}
export function signIn(): Promise<Response> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
token: 'ajsdhflajkshdkfljaldsfhkajsfdklaskdjhlajsdhf',
user: {
name: 'Silvio',
email: '[email protected]',
},
});
}, 2000);
});
}
src/routes/index.tsx
import React from 'react';
import AuthRoutes from './auth.routes';
import AppRoutes from './app.routes';
const Routes: React.FC = () => {
return <AuthRoutes />;
}
export default Routes;
import React from 'react';
import {View, Button, StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});
const SignIn: React.FC = () => (
<View style={styles.container}>
<Button title="Sign In" onPress={() => {}} />
</View>
);
export default SignIn;
...
import Routes from './routes/index';
// FC = Function Component
const App: React.FC = () => {
return (
<NavigationContainer>
<Routes />
</NavigationContainer>
);
};
...
...
import { signIn } from '../../services/auth';
...
const SignIn: React.FC = () => {
async function handleSignIn() {
const response = await signIn();
console.log(response);
}
return (
<View style={styles.container}>
<Button title="Sign In" onPress={handleSignIn} />
</View>
);
};
...
mkdir src/contexts
touch src/contexts/auth.tsx
import { createContext } from 'react';
const AuthContext = createContext({signed: true});
export default AuthContext;
...
import AuthContext from './contexts/auth';
// FC = Function Component
const App: React.FC = () => {
return (
<NavigationContainer>
<AuthContext.Provider value={{signed: true}}>
<Routes />
</AuthContext.Provider>
</NavigationContainer>
);
};
...
import React, {useContext} from 'react';
import {View, Button, StyleSheet} from 'react-native';
import { signIn } from '../../services/auth';
import AuthContext from '../../contexts/auth';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});
const SignIn: React.FC = () => {
const {signed} = useContext(AuthContext);
console.log(signed);
async function handleSignIn() {
const response = await signIn();
console.log(response);
}
return (
<View style={styles.container}>
<Button title="Sign In" onPress={handleSignIn} />
</View>
);
};
export default SignIn;
Note: console show true as on App.tsx <AuthContext.Provider value={{signed: true}}>
import React, {createContext} from 'react';
const AuthContext = createContext({signed: true});
export const AuthProvider: React.FC = ({children}) => (
<AuthContext.Provider value={{signed: true}}>
{children}
</AuthContext.Provider>
);
export default AuthContext;
...
import { AuthProvider } from './contexts/auth';
...
const App: React.FC = () => {
return (
<NavigationContainer>
<AuthProvider>
<Routes />
</AuthProvider>
</NavigationContainer>
);
};
...
import React, {createContext} from 'react';
import * as auth from '../services/auth';
interface AuthContextData {
signed: boolean;
user: object | null;
signIn(): Promise<void>;
}
const AuthContext = createContext<AuthContextData>({} as AuthContextData);
export const AuthProvider: React.FC = ({children}) => {
async function signIn() {
const response = await auth.signIn();
console.log(response);
}
return (
<AuthContext.Provider value={{signed: false, user: {}, signIn}}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
...
const SignIn: React.FC = () => {
const {signed, user, signIn} = useContext(AuthContext);
console.log(signed);
console.log(user);
async function handleSignIn() {
await signIn();
}
return (
<View style={styles.container}>
<Button title="Sign In" onPress={handleSignIn} />
</View>
);
};
...
import React, {createContext, useState} from 'react';
import * as auth from '../services/auth';
interface AuthContextData {
signed: boolean;
user: object | null;
signIn(): Promise<void>;
}
const AuthContext = createContext<AuthContextData>({} as AuthContextData);
export const AuthProvider: React.FC = ({children}) => {
const [user, setUser] = useState<object | null>(null);
async function signIn() {
const response = await auth.signIn();
setUser(response.user);
console.log(response);
}
return (
<AuthContext.Provider value={{signed: !!user, user: user, signIn}}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
import React, {useContext} from 'react';
import AuthContext from '../contexts/auth';
import AuthRoutes from './auth.routes';
import AppRoutes from './app.routes';
const Routes: React.FC = () => {
const {signed} = useContext(AuthContext);
return signed ? <AppRoutes /> : <AuthRoutes />;
};
export default Routes;
...
interface AuthContextData {
signed: boolean;
user: object | null;
signIn(): Promise<void>;
signOut(): void;
}
...
...
export const AuthProvider: React.FC = ({children}) => {
...
function signOut() {
setUser(null);
}
return (
<AuthContext.Provider value={{signed: !!user, user: user, signIn, signOut}}>
{children}
</AuthContext.Provider>
);
};
...
import React, {useContext} from 'react';
import {View, Button, StyleSheet} from 'react-native';
import AuthContext from '../../contexts/auth';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});
const Dashboard: React.FC = () => {
const {signOut} = useContext(AuthContext);
async function handleSignOut() {
await signOut();
}
return (
<View style={styles.container}>
<Button title="Sign Out" onPress={handleSignOut} />
</View>
);
};
export default Dashboard;
yarn add @react-native-community/async-storage
npx pod-install ios
yarn ios
import React, {createContext, useState, useEffect} from 'react';
import AsyncStorage from '@react-native-community/async-storage';
import * as auth from '../services/auth';
interface AuthContextData {
signed: boolean;
user: object | null;
signIn(): Promise<void>;
signOut(): void;
}
const AuthContext = createContext<AuthContextData>({} as AuthContextData);
export const AuthProvider: React.FC = ({children}) => {
const [user, setUser] = useState<object | null>(null);
useEffect(() => {
async function loadStorageData() {
// TODO: Try using multiget instead of 2 awaits
const storageUser = await AsyncStorage.getItem('@reactNativeAuth:user');
const storageToken = await AsyncStorage.getItem('@reactNativeAuth:token');
if (storageUser && storageToken) {
setUser(JSON.parse(storageUser));
}
}
loadStorageData();
});
async function signIn() {
const response = await auth.signIn();
setUser(response.user);
await AsyncStorage.setItem(
'@reactNativeAuth:user',
JSON.stringify(response.user),
);
await AsyncStorage.setItem('@reactNativeAuth:token', response.token);
}
function signOut() {
AsyncStorage.clear().then(() => {
setUser(null);
});
}
return (
<AuthContext.Provider value={{signed: !!user, user: user, signIn, signOut}}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
import { View, ActivityIndicator } from 'react-native';
...
const [loading, setLoading] = useState(true);
...
...
// A kind of sleep
await new Promise((resolve) => setTimeout(resolve, 2000));
if (storageUser && storageToken) {
setUser(JSON.parse(storageUser));
setLoading(false);
}
...
...
if (loading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large" color="#666" />
</View>
)
}
return (
<AuthContext.Provider value={{signed: !!user, user: user, signIn, signOut}}>
{children}
</AuthContext.Provider>
);
...
interface AuthContextData {
signed: boolean;
user: object | null;
loading: boolean;
signIn(): Promise<void>;
signOut(): void;
}
...
const [loading, setLoading] = useState(true);
...
...
// A kind of sleep
await new Promise((resolve) => setTimeout(resolve, 2000));
if (storageUser && storageToken) {
setUser(JSON.parse(storageUser));
setLoading(false);
}
...
...
import {View, ActivityIndicator} from 'react-native';
...
...
const Routes: React.FC = () => {
const {signed, loading} = useContext(AuthContext);
if (loading) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large" color="#666" />
</View>
);
}
return signed ? <AppRoutes /> : <AuthRoutes />;
};
...
yarn add axios
touch src/services/api.ts
import axios from 'axios';
const api = axios.create({
baseURL: 'http://127.0.0.1:8000/api/',
});
export default api;
...
import api from '../services/api';
...
...
useEffect(() => {
async function loadStorageData() {
// TODO: Try using multiget instead of 2 awaits
const storageUser = await AsyncStorage.getItem('@reactNativeAuth:user');
const storageToken = await AsyncStorage.getItem('@reactNativeAuth:token');
// A kind of sleep
await new Promise((resolve) => setTimeout(resolve, 2000));
if (storageUser && storageToken) {
api.defaults.headers['Authorization'] = `Bearer ${storageToken}`;
setUser(JSON.parse(storageUser));
setLoading(false);
}
}
loadStorageData();
});
async function signIn() {
const response = await auth.signIn();
setUser(response.user);
api.defaults.headers['Authorization'] = `Bearer ${response.token}`;
await AsyncStorage.setItem(
'@reactNativeAuth:user',
JSON.stringify(response.user),
);
await AsyncStorage.setItem('@reactNativeAuth:token', response.token);
}
...
src/contexts/auth.tsx
import React, {createContext, useState, useEffect, useContext} from 'react';
...
const AuthContext = createContext<AuthContextData>({} as AuthContextData);
export const AuthProvider: React.FC = ({children}) => {
...
};
export function useAuth() {
const context = useContext(AuthContext);
return context;
}
SignIn
Dashboard
Routes
Everything created in this article can be aply to ReactJS. Just AsyncStorage must be localStorage.