React: Simple Auth Flow
Tags: tutorial, beginners, react
Published: 2020-03-23 17:03:40

Now that we know how to use `useState`, `useReducer` and Context, how can we put these concepts into our projects? An easy example is to create a simple authentication flow.

We'll first setup the `UserContext` using React Context. 

```react
import { createContext } from 'react'

const UserContext = createContext({
  user: null,
  hasLoginError: false,
  login: () => null,
  logout: () => null
})

export default UserContext
```

Now that we've created a context, we can start using it in our wrapping component. We'll also use `useReducer` to keep the state of our context.

```react
import UserContext from './UserContext'

const INITIAL_STATE = {
  user: null,
  hasLoginError: false
}

const reducer = (state, action) => { ... }

const App = () => {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE)

  return (
    <UserContext.Provider>
      ...
    </UserContext.Provider>
  )
}
```

Our reducer will handle 2 action types -- `login` and `logout`.

```react
const reducer = (state, action) => {
  switch(action.type) {
    case 'login': {
      const { username, password } = action.payload
      if (validateCredentials(username, password)) {
        return {
          ...state,
          hasLoginError: false,
          user: {} // assign user here
        }
      }

      return {
        ...state,
        hasLoginError: true,
        user: null
      }
    }
    case 'logout':
      return {
        ...state,
        user: null
      }
    default:
      throw new Error(`Invalid action type: ${action.type}`)
  }
}
```

After implementing the reducer, we can use `dispatch` to call these actions. We'll create functions that we'll pass to our provider's value.

```react
...
const login = (username, password) => {
  dispatch({ type: 'login', payload: { username, password } })
}
const logout = () => {
  dispatch({ type: 'logout' })
}

const value = {
  user: state.user,
  hasLoginError: state.hasLoginError,
  login,
  logout
}

return (
  <UserContext.Provider value={value}>
    ...
  </UserContext.Provider>
)
```

Now that our value gets updated when our state updates, and we passed the login and logout function; we'll have access to those values in our subsequent child components.

We'll make two components -- `LoginForm` and `UserProfile`. We'll render the form when there's no user and the profile when a user is logged in.

```react
...
<UserContext.Provider value={value}>
  {user && <UserProfile />}
  {!user && <LoginForm />}
</UserContext.Provider>
...
```

Let's start with the login form, we'll use `useState` to manage our form's state. We'll also grab the context so we have access to `login` and `hasLoginError`.

```react
const { login, hasLoginError } = useContext(UserContext)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')

const onUsernameChange = evt => setUsername(evt.target.value)
const onPasswordChange = evt => setPassword(evt.target.value)
const onSubmit = (evt) => {
  evt.preventDefault()
  login(username, password)
}

return (
  <form onSubmit={onSubmit}>
    ...
    {hasLoginError && <p>Error Logging In</p>}
    <input type='text' onChange={onUsernameChange} />
    <input type='password' onChange={onPasswordChange} />
    ...
  </form>
)
```

If we're logged in we need access to the user object and the logout function.

```react
const { logout, user } = useContext(UserContext)

return (
  <>
    <h1>Welcome {user.username}</h1>
    <button onClick={logout}>Logout</button>
  </>
)
```

Now, you have a simple authentication flow in React using different ways we can manage our state!

[Code Sandbox](https://codesandbox.io/s/react-context-authentication-otjqv)