The useBlocker hook allows you to block navigation when certain conditions are met, such as when a user has unsaved changes in a form. This provides a way to prompt users before they accidentally leave a page with unsaved work.
import { useBlocker } from 'one'
import { useState } from 'react'
export default function EditPage() {
const [isDirty, setIsDirty] = useState(false)
const blocker = useBlocker(isDirty)
return (
<>
<form onChange={() => setIsDirty(true)}>
<input type="text" />
<button type="submit">Save</button>
</form>
{blocker.state === 'blocked' && (
<div className="modal">
<p>You have unsaved changes. Leave anyway?</p>
<button onClick={blocker.reset}>Stay</button>
<button onClick={blocker.proceed}>Leave</button>
</div>
)}
</>
)
}
For more control, pass a function that receives location information:
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
// Only block when leaving the /edit section
return currentLocation.startsWith('/edit') && !nextLocation.startsWith('/edit')
})
The function receives:
currentLocation - The current pathname and search paramsnextLocation - Where the user is trying to navigatehistoryAction - The type of navigation ('push', 'pop', or 'replace')The blocker returns an object with a state property:
'unblocked'Navigation is not blocked. The blocker object has no additional properties.
if (blocker.state === 'unblocked') {
// Normal state, navigation is allowed
}
'blocked'Navigation has been blocked and is waiting for user decision.
if (blocker.state === 'blocked') {
blocker.location // The destination the user tried to navigate to
blocker.reset() // Cancel the navigation, stay on current page
blocker.proceed() // Allow the navigation to continue
}
'proceeding'The user has chosen to proceed and navigation is in progress.
if (blocker.state === 'proceeding') {
blocker.location // Where we're navigating to
}
On web, useBlocker intercepts:
On native platforms, useBlocker uses React Navigation's beforeRemove event. This blocks:
Note: Native blocking cannot prevent the app from being closed or backgrounded.
import type { Blocker, BlockerFunction, BlockerState } from 'one'
// BlockerState is 'unblocked' | 'blocked' | 'proceeding'
// BlockerFunction signature
type BlockerFunction = (args: {
currentLocation: string
nextLocation: string
historyAction: 'push' | 'pop' | 'replace'
}) => boolean
import { useBlocker } from 'one'
import { useState } from 'react'
export default function EditProfile() {
const [formData, setFormData] = useState({ name: '', email: '' })
const [savedData, setSavedData] = useState({ name: '', email: '' })
// Check if form has unsaved changes
const hasUnsavedChanges =
formData.name !== savedData.name ||
formData.email !== savedData.email
const blocker = useBlocker(hasUnsavedChanges)
const handleSave = () => {
// Save logic here...
setSavedData(formData)
}
return (
<div>
<h1>Edit Profile</h1>
<input
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
placeholder="Name"
/>
<input
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
placeholder="Email"
/>
<button onClick={handleSave}>Save Changes</button>
{blocker.state === 'blocked' && (
<div className="dialog-overlay">
<div className="dialog">
<h2>Unsaved Changes</h2>
<p>You have unsaved changes that will be lost.</p>
<div className="dialog-actions">
<button onClick={blocker.reset}>
Keep Editing
</button>
<button onClick={blocker.proceed}>
Discard Changes
</button>
</div>
</div>
</div>
)}
</div>
)
}
Edit this page on GitHub.