User authentication in React

The original address: kentcdodds.com/blog/authen…

This article shows how to use Context and Hooks to manage user authentication in React development.

Say first conclusion

Here is a simplified version of the final implementation of this article, so that you can see the final result directly:

import React from 'react'
import {useUser} from './context/auth'
import AuthenticatedApp from './authenticated-app'
import UnauthenticatedApp from './unauthenticated-app'
function App() {
  const user = useUser()
  return user ? <AuthenticatedApp /> : <UnauthenticatedApp />
}
export App
Copy the code

Well, the final code looks something like this. Most applications that require user authentication management can use logic similar to the above to manage user login status. When a user visits a log-in page in our application, we can redirect the user to the landing page, which we do in most cases, and we can also show the interface to the user who is not logged in without skipping to the page. To improve the user experience, we can also do this:

import React from 'react'
import {useUser} from './context/auth'
const AuthenticatedApp = React.lazy((a)= > import('./authenticated-app'))
const UnauthenticatedApp = React.lazy((a)= > import('./unauthenticated-app'))
function App() {
  const user = useUser()
  return user ? <AuthenticatedApp /> : <UnauthenticatedApp />
}
export App
Copy the code

Pro, lazy loading code to achieve: not logged in users visit our page, will only load rendering not logged in the interface code; If a logged-in user visits the same page, only the code that renders the logged-in interface will be loaded.

It is entirely up to the developer to decide what interface to render in the AuthenticatedApp and UnauthenticatedApp. Maybe they’ll render some routers or even reuse some common components. Regardless of what we render, we don’t need to worry about the user’s login state, because we already know the user’s login state when we render one of these components.

How to implement the above logic step by step

If you want to see the final implementation of the entire APP, you can check it out on Github.

OK, let’s see how to implement the above logic step by step. First, let’s look at the entry code for our app:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
import AppProviders from './context'
ReactDOM.render(
  <AppProviders>
    <App />
  </AppProviders>.document.getElementById('root'),Copy the code

Here is the code for the AppProviders component:

import React from 'react'
import {AuthProvider} from './auth-context'
import {UserProvider} from './user-context'
function AppProviders({children}) {
  return (
    <AuthProvider>
      <UserProvider>{children}</UserProvider>
    </AuthProvider>)}export default AppProviders
Copy the code

OK, we have two providers: one is to maintain the application’s authentication status; The other is to maintain data for the currently logged in user. The AppProvider is literally responsible for initializing data for the entire APP (if a user’s authenticated token exists in localStorage, then we can retrieve some user data directly from the token). UserProvider, on the other hand, is responsible for keeping changes we make to user data (such as email addresses, CVS, etc.) in sync with the server.

The full auth-context.js contains some logic that is irrelevant to the topic of this article, so let’s look at the simplified version of auth-context.js:

import React from 'react'
import {FullPageSpinner} from '.. /components/lib'
const AuthContext = React.createContext()
function AuthProvider(props) {
  // If we are not sure whether the current user is logged in, for example, we are still asking the backend interface to query the login status.
  // Then we render a global load instead of loading the actual page component
  if (weAreStillWaitingToGetTheUserData) {
    return<FullPageSpinner /> } const login = () => {} // make a login request const register = () => {} // register the user Const logout = () => {} // Clear the token in localStorage and the user data Here I don't use 'React. UseMemo' to optimize provider's 'value'. Render return (<AuthContext.Provider value={{data, login, logout, register}} {... props} /> ) } const useAuth = () => React.useContext(AuthContext) export {AuthProvider, UseAuth} // user-context.js' UserProvider 'looks like this:  // const UserProvider = props => ( // <UserContext.Provider value={useAuth().data.user} {... props} /> // ) // and the useUser hook is basically this: // const useUser = () => React.useContext(UserContext)Copy the code

The key to simplifying authentication management in our application is:

The component responsible for maintaining the login state of the user does not render the main content of the page until the login state of the current user is obtained. It can render a global loading. Render the body of the page only after the user’s login status is retrieved from the server: render logged components; Unlogged Render unlogged.

conclusion

In actual development, many apps face different scenarios. If you use server-side rendering (SSR), you probably don’t need an in-load loading because you already know on the server whether the current user is logged in or not. Even in this scenario, the user login status can be managed by bringing it out to the global Provider, which increases the maintainability of our code.

PS:

Some students asked the same question: If we have an application where logged-in and logged-in users see many of the same screens (like Twitter), as opposed to gmail, where logged-in and logged-in users see completely different screens, how can we maintain user logged-in status?

If this is the case, many components in the code will use the useUser hook, in order to be able to execute different logic in a common component depending on whether you are logged in or not. Since our common component might care if the user is logged in, you can even wrap a useIsAuthenticated hook that returns a Boolean value indicating whether the user is logged in. Implementing this logic is very simple, thanks to Context and Hooks.