Skip to content

Commit 826bd49

Browse files
committed
Refactor auth flow guide
1 parent 9c5af11 commit 826bd49

File tree

2 files changed

+95
-90
lines changed

2 files changed

+95
-90
lines changed

website/versioned_docs/version-5.x/auth-flow.md

Lines changed: 78 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,86 @@ Most apps require that a user authenticate in some way to have access to data as
1818

1919
This is the behavior that we want from the authentication flow: when users sign in, we want to throw away the state of the authentication flow and unmount all of the screens related to authentication, and when we press the hardware back button we expect to not be able to go back to the authentication flow.
2020

21+
## Conditionally define our screens
22+
23+
In our navigator, we can conditionally render appropriate screens. For our case, let's say we have 3 screens:
24+
25+
- `SplashScreen` - This will show a splash or loading screen when we're restoring the token.
26+
- `SignInScreen` - This is the screen we show if the user isn't signed in already (we couldn't find a token).
27+
- `HomeScreen` - This is the screen we show if the user is already signed in.
28+
29+
So our navigator will look like:
30+
31+
```js
32+
if (state.isLoading) {
33+
// We haven't finished checking for the token yet
34+
return <SplashScreen />;
35+
}
36+
37+
return (
38+
<Stack.Navigator>
39+
{state.userToken == null ? (
40+
// No token found, user isn't signed in
41+
<Stack.Screen
42+
name="SignIn"
43+
component={SignInScreen}
44+
options={{
45+
title: 'Sign in',
46+
// When logging out, a pop animation feels intuitive
47+
// You can remove this if you want the default 'push' animation
48+
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
49+
}}
50+
/>
51+
) : (
52+
// User is signed in
53+
<Stack.Screen name="Home" component={HomeScreen} />
54+
)}
55+
</Stack.Navigator>
56+
);
57+
```
58+
59+
In the above snippet, `isLoading` means that we're still checking if we have a token. This can usually be done by checking if we have a token in `AsyncStorage` and validating if token. After we get the token and if it's valid, we need to set the `userToken`. We also have another state called `isSignout` to have a different animation on sign out.
60+
61+
The main thing to notice is that we're conditionally defining screens based on these state variables:
62+
63+
- `SignIn` screen is only defined if `userToken` is `null` (user is not signed in)
64+
- `Home` screen is only defined if `userToken` is non-null (user is signed in)
65+
66+
This takes advantage of a new feature in React Navigation: being able to dynamically define and alter the screen definitions of a navigator based on props or state. The example shows stack navigator, but you can use the same approach with any navigator.
67+
68+
Here, we're conditionally defining one screen for each case. But you could also define multiple screens. For example, you probably want to define password reset, signup, etc screens as well when the user isn't signed in. Similarly for the screens accessible after sign in, you probably have more than one screen. We can use `React.Fragment` to define multiple screens:
69+
70+
```js
71+
state.userToken == null ? (
72+
<>
73+
<Stack.Screen name="SignIn" component={SignInScreen} />
74+
<Stack.Screen name="SignUp" component={SignUpScreen} />
75+
<Stack.Screen name="ResetPassword" component={ResetPassword} />
76+
</>
77+
) : (
78+
<>
79+
<Stack.Screen name="Home" component={HomeScreen} />
80+
<Stack.Screen name="Profile" component={ProfileScreen} />
81+
</>
82+
);
83+
```
84+
85+
This pattern has been in use by other routing libraries such as React Router for a long time, and is commonly knows as "Protected routes". Here, our screens which need the user to be logged in are "protected" and cannot be navigated to by other means if the user is not logged in.
86+
2187
## Implement the logic for restoring the token
2288

23-
In our component, we'll keep 2 states:
89+
From the previous snippet, we can see that we need 3 state variables:
2490

2591
- `isLoading` - We set this to `true` when we're trying to check if we already have a token saved in `AsyncStorage`
92+
- `isSignout` - We set this to `true` when user is signing out, otherwise set it to `false`
2693
- `userToken` - The token for the user. If it's non-null, we assume the user is logged in, otherwise not.
2794

2895
So we need to:
2996

3097
- Add some logic for restoring token, sign in and sign out
3198
- Expose methods for sign in and sign out to other components
3299

33-
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. In fact, in bigger apps, a global state management library is more suitable for storing authentication tokens.
100+
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. In fact, in bigger apps, a global state management library is more suitable for storing authentication tokens. You can adapt the same approach to your state management library.
34101

35102
First we'll need to create a context for auth where we can expose necessary methods:
36103

@@ -42,6 +109,8 @@ const AuthContext = React.createContext();
42109

43110
So our component will look like this:
44111

112+
<samp id="auth-flow" />
113+
45114
```js
46115
import * as React from 'react';
47116
import AsyncStorage from '@react-native-community/async-storage';
@@ -123,99 +192,20 @@ export default function App({ navigation }) {
123192

124193
return (
125194
<AuthContext.Provider value={authContext}>
126-
{/* We'll render navigator content here */}
195+
<Stack.Navigator>
196+
{state.userToken == null ? (
197+
<Stack.Screen name="SignIn" component={SignInScreen} />
198+
) : (
199+
<Stack.Screen name="Home" component={HomeScreen} />
200+
)}
201+
</Stack.Navigator>
127202
</AuthContext.Provider>
128203
);
129204
}
130205
```
131206

132-
## Render the navigator content
133-
134-
In our navigator, we can conditionally render appropriate screens. For our case, let's say we have 3 screens:
135-
136-
- `SplashScreen` - This will show a splash or loading screen when we're restoring the token.
137-
- `SignInScreen` - This is the screen we show if the user isn't signed in already (we couldn't find a token).
138-
- `HomeScreen` - This is the screen we show if the user is already signed in.
139-
140-
So our navigator will look like:
141-
142-
<samp id="auth-flow" />
143-
144-
```js
145-
if (state.isLoading) {
146-
// We haven't finished checking for the token yet
147-
return <SplashScreen />;
148-
}
149-
150-
return (
151-
<AuthContext.Provider value={authContext}>
152-
<Stack.Navigator>
153-
{state.userToken == null ? (
154-
// No token found, user isn't signed in
155-
<Stack.Screen
156-
name="SignIn"
157-
component={SignInScreen}
158-
options={{
159-
title: 'Sign in',
160-
// When logging out, a pop animation feels intuitive
161-
// You can remove this if you want the default 'push' animation
162-
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
163-
}}
164-
/>
165-
) : (
166-
// User is signed in
167-
<Stack.Screen name="Home" component={HomeScreen} />
168-
)}
169-
</Stack.Navigator>
170-
</AuthContext.Provider>
171-
);
172-
```
173-
174-
In the above code snippet, we're conditionally defining screens:
175-
176-
- `SignIn` screen is only defined if `userToken` is `null`
177-
- `Home` screen is only defined if `userToken` is non-null
178-
179-
This takes advantage of a new feature in React Navigation: being able to dynamically define and alter the screen definitions of a navigator based on props or state. The example shows stack navigator, but you can use the same approach with any navigator.
180-
181-
> Note: The following explanation is useful for people coming from older versions of React Navigation. If React Navigation 5 is your first version, then you can skip to the next section.
182-
183-
In earlier versions of React Navigation, there were 2 ways to handle this:
184-
185-
1. Keep multiple navigators and use switch navigator to switch the active navigator to a different one upon login (recommended)
186-
2. Reset the state of the navigator to the desired screens upon login
187-
188-
Both of these approaches were imperative. We needed to update the state to save your token, and then do a `navigate` or `reset` to change screens manually. Seems reasonable, right? But what happens when the user logs out? We need to update the state to delete the token, then `navigate` or `reset` again manually to show the login screen. We have to imperatively do the task twice already. Add more scenarios to this (e.g. unverified user, guest etc.) and it becomes even more complex.
189-
190-
But with the above approach, you can declaratively say which screens should be accessible if user is logged in and which screens shouldn't be. If the user logs in or logs out, you update the `userToken` in state and the correct screens are shown automatically.
191-
192-
To summarize the benefits:
193-
194-
- No need for manually navigating to correct screen on log in or log out, correct screens are shown automatically.
195-
- If the user is not logged in, it's impossible to navigate to screens which need the user to be logged in (e.g. from a deep link, restoring persisted state), which means you don't need to deal with inconsistent states.
196-
- Since all our screens are under the stack navigator, we get smooth animations after log in or log out unlike the abrupt screen change with switch navigator.
197-
198-
This pattern has been in use by other routing libraries such as React Router for a long time, and is commonly knows as "Protected routes". Here, our screens which need the user to be logged in are "protected" and cannot be navigated to by other means if the user is not logged in.
199-
200207
## Fill in other components
201208

202-
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:
203-
204-
```js
205-
state.userToken == null ? (
206-
<>
207-
<Stack.Screen name="SignIn" component={SignInScreen} />
208-
<Stack.Screen name="SignUp" component={SignUpScreen} />
209-
<Stack.Screen name="ResetPassword" component={ResetPassword} />
210-
</>
211-
) : (
212-
<>
213-
<Stack.Screen name="Home" component={HomeScreen} />
214-
<Stack.Screen name="Profile" component={ProfileScreen} />
215-
</>
216-
);
217-
```
218-
219209
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.
220210

221211
```js

website/versioned_docs/version-5.x/upgrading-from-4.x.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,24 @@ export default function App() {
304304
}
305305
```
306306
307-
The new approach is more maintainable and removes the need for something like Switch Navigator. So it has been removed.
307+
In earlier versions of React Navigation, there were 2 ways to handle this:
308308
309-
See [Authentication flows](auth-flow.html) for more info on implementing authentication flows.
309+
1. Keep multiple navigators and use switch navigator to switch the active navigator to a different one upon login (recommended)
310+
2. Reset the state of the navigator to the desired screens upon login
311+
312+
Both of these approaches were imperative. We needed to update the state to save your token, and then do a `navigate` or `reset` to change screens manually. Seems reasonable, right? But what happens when the user logs out? We need to update the state to delete the token, then `navigate` or `reset` again manually to show the login screen. We have to imperatively do the task twice already. Add more scenarios to this (e.g. unverified user, guest etc.) and it becomes even more complex.
313+
314+
But with the above approach, you can declaratively say which screens should be accessible if user is logged in and which screens shouldn't be. If the user logs in or logs out, you update the `userToken` in state and the correct screens are shown automatically.
315+
316+
To summarize the benefits:
317+
318+
- No need for manually navigating to correct screen on log in or log out, correct screens are shown automatically.
319+
- If the user is not logged in, it's impossible to navigate to screens which need the user to be logged in (e.g. from a deep link, restoring persisted state), which means you don't need to deal with inconsistent states.
320+
- Since all our screens are under the stack navigator, we get smooth animations after log in or log out unlike the abrupt screen change with switch navigator.
321+
322+
So, the new approach covers more edge cased and removes the need for something like Switch Navigator. So it has been removed.
323+
324+
See [Authentication flows](auth-flow.html) for a guide on implementing authentication flows.
310325
311326
## Global props with `screenProps`
312327

0 commit comments

Comments
 (0)