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!
Top comments (5)
Great work..
Thanks!
Karl.U welcome. I have a some problems. Can u help me ? Thanks.
Where do I get dispatch from while calling action? Sorry for the dumb question, I'm coming from redux.
The dispatch is coming from
useReducer. You can check out this post for more info aboutuseReducer.React: useReducer
Karl Castillo ・ Mar 22 ・ 2 min read