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