Skip to content

Commit 79963d1

Browse files
committed
Update auth flow guide
1 parent baeec50 commit 79963d1

File tree

2 files changed

+139
-73
lines changed

2 files changed

+139
-73
lines changed

docs/auth-flow.md

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ In our component, we'll keep 2 states:
2424
- `isLoading` - We set this to `true` when we're trying to check if we already have a token saved in `AsyncStorage`
2525
- `userToken` - The token for the user. If it's non-null, we assume the user is logged in, otherwise not.
2626

27+
So we need to:
28+
29+
- Add some logic for restoring token, sign in and sign out
30+
- Expose methods for sign in and sign out to other components
31+
32+
We'll use `React.useReducer` and `React.useContext` in this guide. But if you're using a state management library such as Redux or Mobx, you can use them for this functionality instead.
33+
34+
First we'll need to create a context for auth where we can expose necessary methods:
35+
36+
```js
37+
import * as React from 'react';
38+
39+
const AuthContext = React.createContext();
40+
```
41+
2742
So our component will look like this:
2843

2944
```js
@@ -79,8 +94,33 @@ export default function App({ navigation }) {
7994
bootstrapAsync();
8095
}, []);
8196

97+
const authContext = React.useMemo(
98+
() => ({
99+
signIn: async data => {
100+
// In a production app, we need to send some data (usually username, password) to server and get a token
101+
// We will also need to handle errors if sign in failed
102+
// After getting token, we need to persist the token using `AsyncStorage`
103+
// In the example, we'll use a dummy token
104+
105+
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
106+
},
107+
signOut: () => dispatch({ type: 'SIGN_OUT' }),
108+
signUp: async data => {
109+
// In a production app, we need to send user data to server and get a token
110+
// We will also need to handle errors if sign up failed
111+
// After getting token, we need to persist the token using `AsyncStorage`
112+
// In the example, we'll use a dummy token
113+
114+
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
115+
},
116+
}),
117+
[]
118+
);
119+
82120
return (
83-
/* We'll render navigator content here */
121+
<AuthContext.Provider value={authContext}>
122+
{/* We'll render navigator content here */}
123+
</AuthContext.Provider>
84124
);
85125
}
86126
```
@@ -99,27 +139,20 @@ So our navigator will look like:
99139

100140
```js
101141
return (
102-
<Stack.Navigator>
103-
{state.isLoading ? (
104-
// We haven't finished checking for the token yet
105-
<Stack.Screen name="Splash" component={SplashScreen} />
106-
) : state.userToken === null ? (
107-
// No token found, user isn't signed in
108-
<Stack.Screen name="SignIn">
109-
{() => (
110-
<SignInScreen
111-
onSignIn={token => {
112-
// In a real app, you should also store this token in async storage
113-
dispatch({ type: 'SIGN_IN', token });
114-
}}
115-
/>
116-
)}
117-
</Stack.Screen>
118-
) : (
119-
// User is signed in
120-
<Stack.Screen name="Home" component={HomeScreen} />
121-
)}
122-
</Stack.Navigator>
142+
<AuthContext.Provider value={authContext}>
143+
<Stack.Navigator>
144+
{state.isLoading ? (
145+
// We haven't finished checking for the token yet
146+
<Stack.Screen name="Splash" component={SplashScreen} />
147+
) : state.userToken === null ? (
148+
// No token found, user isn't signed in
149+
<Stack.Screen name="SignIn" component={SignInScreen} />
150+
) : (
151+
// User is signed in
152+
<Stack.Screen name="Home" component={HomeScreen} />
153+
)}
154+
</Stack.Navigator>
155+
</AuthContext.Provider>
123156
);
124157
```
125158

@@ -149,14 +182,10 @@ Another advantage of this approach is that all the screens are still under the s
149182
We're conditionally defining one screen for each case here. But you could define multiple screens here too. For example, you probably want to defined password reset, signup, etc screens as well when the user isn't signed in. Similarly for your app, you probably have more than one screen. We can use `React.Fragment` - to define multiple screens:
150183

151184
```js
152-
state.userToken === null ? (
185+
state.userToken == null ? (
153186
<>
154-
<Stack.Screen name="SignIn">
155-
{() => <SignInScreen onSignIn={setUserToken} />}
156-
</Stack.Screen>
157-
<Stack.Screen name="SignUp">
158-
{() => <SignUpScreen setUserToken={setUserToken} />}
159-
</Stack.Screen>
187+
<Stack.Screen name="SignIn" component={SignInScreen} />
188+
<Stack.Screen name="SignUp" component={SignUpScreen} />
160189
<Stack.Screen name="ResetPassword" component={ResetPassword} />
161190
</>
162191
) : (
@@ -170,16 +199,26 @@ state.userToken === null ? (
170199
We won't talk about how to implement the text inputs and buttons for the authentication screen, that is outside of the scope of navigation. We'll just fill in some placeholder content.
171200

172201
```js
173-
function SignInScreen({ onSignIn }) {
202+
function SignInScreen() {
203+
const [username, setUsername] = React.useState('');
204+
const [password, setPassword] = React.useState('');
205+
206+
const { signIn } = React.useContext(AuthContext);
207+
174208
return (
175209
<View>
176-
<TextInput placeholder="Username" />
177-
<TextInput placeholder="Password" secureTextEntry />
178-
<Button
179-
title="Sign in"
180-
mode="contained"
181-
onPress={() => onSignIn('token')}
210+
<TextInput
211+
placeholder="Username"
212+
value={username}
213+
onChangeText={setUsername}
214+
/>
215+
<TextInput
216+
placeholder="Password"
217+
value={password}
218+
onChangeText={setPassword}
219+
secureTextEntry
182220
/>
221+
<Button title="Sign in" onPress={() => signIn({ username, password })} />
183222
</View>
184223
);
185224
}

website/static/examples/next/auth-flow.js

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { AsyncStorage, Button, Text, TextInput, View } from 'react-native';
33
import { NavigationNativeContainer } from '@react-navigation/native';
44
import { createStackNavigator } from '@react-navigation/stack';
55

6+
const AuthContext = React.createContext();
7+
68
function SplashScreen() {
79
return (
810
<View>
@@ -12,31 +14,42 @@ function SplashScreen() {
1214
}
1315

1416
function HomeScreen() {
17+
const { signOut } = React.useContext(AuthContext);
18+
1519
return (
1620
<View>
1721
<Text>Signed in!</Text>
22+
<Button title="Sign out" onPress={signOut} />
1823
</View>
1924
);
2025
}
2126

22-
function SignInScreen({ onSignIn }) {
27+
function SignInScreen() {
28+
const [username, setUsername] = React.useState('');
29+
const [password, setPassword] = React.useState('');
30+
31+
const { signIn } = React.useContext(AuthContext);
32+
2333
return (
2434
<View>
25-
<View>
26-
<Text>Name: </Text>
27-
<TextInput placeholder="Username" />
28-
<Text>Password: </Text>
29-
<TextInput placeholder="Password" secureTextEntry />
30-
<Button
31-
title="Sign in"
32-
mode="contained"
33-
onPress={() => onSignIn('token')}
34-
/>
35-
</View>
35+
<TextInput
36+
placeholder="Username"
37+
value={username}
38+
onChangeText={setUsername}
39+
/>
40+
<TextInput
41+
placeholder="Password"
42+
value={password}
43+
onChangeText={setPassword}
44+
secureTextEntry
45+
/>
46+
<Button title="Sign in" onPress={() => signIn({ username, password })} />
3647
</View>
3748
);
3849
}
3950

51+
const Stack = createStackNavigator();
52+
4053
export default function App({ navigation }) {
4154
const [state, dispatch] = React.useReducer(
4255
(prevState, action) => {
@@ -55,7 +68,7 @@ export default function App({ navigation }) {
5568
case 'SIGN_OUT':
5669
return {
5770
...prevState,
58-
userToken: undefined,
71+
userToken: null,
5972
};
6073
}
6174
},
@@ -86,31 +99,45 @@ export default function App({ navigation }) {
8699
bootstrapAsync();
87100
}, []);
88101

89-
const Stack = createStackNavigator();
102+
const authContext = React.useMemo(
103+
() => ({
104+
signIn: async data => {
105+
// In a production app, we need to send some data (usually username, password) to server and get a token
106+
// We will also need to handle errors if sign in failed
107+
// After getting token, we need to persist the token using `AsyncStorage`
108+
// In the example, we'll use a dummy token
109+
110+
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
111+
},
112+
signOut: () => dispatch({ type: 'SIGN_OUT' }),
113+
signUp: async data => {
114+
// In a production app, we need to send user data to server and get a token
115+
// We will also need to handle errors if sign up failed
116+
// After getting token, we need to persist the token using `AsyncStorage`
117+
// In the example, we'll use a dummy token
118+
119+
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
120+
},
121+
}),
122+
[]
123+
);
90124

91125
return (
92-
<NavigationNativeContainer>
93-
<Stack.Navigator>
94-
{state.isLoading ? (
95-
// We haven't finished checking for the token yet
96-
<Stack.Screen name="Splash" component={SplashScreen} />
97-
) : state.userToken === null ? (
98-
// No token found, user isn't signed in
99-
<Stack.Screen name="SignIn">
100-
{() => (
101-
<SignInScreen
102-
onSignIn={token => {
103-
// In a real app, you should also store this token in async storage
104-
dispatch({ type: 'SIGN_IN', token });
105-
}}
106-
/>
107-
)}
108-
</Stack.Screen>
109-
) : (
110-
// User is signed in
111-
<Stack.Screen name="Home" component={HomeScreen} />
112-
)}
113-
</Stack.Navigator>
114-
</NavigationNativeContainer>
126+
<AuthContext.Provider value={authContext}>
127+
<NavigationNativeContainer>
128+
<Stack.Navigator>
129+
{state.isLoading ? (
130+
// We haven't finished checking for the token yet
131+
<Stack.Screen name="Splash" component={SplashScreen} />
132+
) : state.userToken == null ? (
133+
// No token found, user isn't signed in
134+
<Stack.Screen name="SignIn" component={SignInScreen} />
135+
) : (
136+
// User is signed in
137+
<Stack.Screen name="Home" component={HomeScreen} />
138+
)}
139+
</Stack.Navigator>
140+
</NavigationNativeContainer>
141+
</AuthContext.Provider>
115142
);
116143
}

0 commit comments

Comments
 (0)