Karl Castillo
Posted on March 23, 2020
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.
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.
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
.
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.
...
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.
...
<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
.
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.
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!
Posted on March 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 26, 2023