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 signaturetype BlockerFunction = (args: { currentLocation: string nextLocation: string historyAction: 'push' | 'pop' | 'replace'}) => booleanimport { 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.