When working with Next.js and Redux, there are times you’ll want to ensure an action is fully completed before redirecting the user. This is especially true in authentication flows: after a successful login, you’ll want to redirect to the home page, but only if the login completes without any issues.
In this post, we’ll explore how to handle this scenario using Redux Toolkit’s unwrap
function. We'll look at how it helps manage async actions, allowing us to control navigation based on the result of a dispatched action.
Why the Problem Exists
In traditional client-side apps, actions like login often require an asynchronous request to an API, and we rely on Redux to handle these actions. In Next.js, navigating from one page to another uses router.push()
, which executes immediately if called. This can cause routing to happen before the login action finishes, potentially leading to redirecting users even if the login failed.
To avoid this, we need to await the login action, ensuring it completes before proceeding to the next page.
Enter unwrap
: Handling Redux Promises Gracefully
Redux Toolkit offers a built-in way to handle promises through unwrap
. This method allows us to await an async action and retrieve its result (either success or failure) without additional boilerplate.
Here’s how we can integrate this with Next.js routing.
Step-by-Step Solution: Waiting for Login Before Routing
- Setting up Redux for Authentication
If you haven’t already set up Redux and a slice for handling login, start by creating a basic async action for authentication.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const login = createAsyncThunk(
'auth/login',
async ({ email, password }, { rejectWithValue }) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
if (!response.ok) throw new Error('Login failed');
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState: { user: null, error: null },
extraReducers: (builder) => {
builder
.addCase(login.fulfilled, (state, action) => {
state.user = action.payload;
})
.addCase(login.rejected, (state, action) => {
state.error = action.payload;
});
},
});
export default authSlice.reducer;
- Awaiting the Action Dispatch in Your Component
In your login component, we’ll usedispatch
andunwrap
to ensurerouter.push()
only runs after the login action completes successfully. By usingunwrap
, we gain access to the result of the promise and can handle errors directly in the UI if needed.
import { useDispatch } from 'react-redux';
import { useRouter } from 'next/router';
import { login } from '../features/auth/authSlice';
const LoginForm = () => {
const dispatch = useDispatch();
const router = useRouter();
const handleLogin = async ({ email, password }) => {
try {
// Wait for the dispatch to complete
await dispatch(login({ email, password })).unwrap();
// Navigate to the home page after successful login
router.push('/');
} catch (error) {
console.error("Login failed:", error);
// Optionally, display an error message to the user here
}
};
return (
<form onSubmit={handleLogin}>
{/* Your form code here */}
</form>
);
};
export default LoginForm;
- Handling Errors
By wrappingdispatch(login(...)).unwrap()
in atry-catch
block, we can manage errors directly in the component. If the login fails, the error will be caught, allowing us to show an error message or take other appropriate actions without redirecting.
Why This Approach is Effective
Using unwrap
makes async handling in Redux much cleaner. It allows you to:
- Wait for async actions to resolve or reject before continuing.
- Handle errors in the component, so you can display feedback directly to the user.
- Prevent unwanted redirects by only navigating on success.
Wrapping Up
With this approach, handling async actions in Redux with Next.js becomes more predictable and manageable. Waiting for async actions before navigating helps you create a smoother user experience and better error handling.
By leveraging unwrap
, you gain greater control over your app’s flow, ensuring that navigation happens only when you want it to.