Skip to content

Commit

Permalink
feature: update api to make it more typesafe
Browse files Browse the repository at this point in the history
  • Loading branch information
tomrehnstrom committed Aug 5, 2024
1 parent 3ac3c8a commit 0550af2
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 62 deletions.
53 changes: 24 additions & 29 deletions examples/react/navigation-blocking/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const rootRoute = createRootRoute({
})

function RootComponent() {
const { proceed, reset, status } = useBlocker({
from: '/editor-1',
to: '/editor-1/editor-2',
blockerFn: () => true,
})

return (
<>
<div className="p-2 flex gap-2 text-lg">
Expand All @@ -38,6 +44,24 @@ function RootComponent() {
</Link>
</div>
<hr />

{status === 'blocked' && (
<div className="mt-2">
<div>Are you sure you want to leave editor 1?</div>
<button
className="bg-lime-500 text-white rounded p-1 px-2 mr-2"
onClick={proceed}
>
YES
</button>
<button
className="bg-red-500 text-white rounded p-1 px-2"
onClick={reset}
>
NO
</button>
</div>
)}
<Outlet />
<TanStackRouterDevtools position="bottom-right" />
</>
Expand Down Expand Up @@ -68,13 +92,6 @@ function Editor1Component() {
const [value, setValue] = React.useState('')
const [useCustomBlocker, setUseCustomBlocker] = React.useState(false)

const { proceed, reset, status } = useBlocker({
blockerFn: useCustomBlocker
? undefined
: () => window.confirm('Are you sure you want to leave editor 1?'),
condition: value,
})

return (
<div className="flex flex-col p-2">
<h3>Editor 1</h3>
Expand All @@ -93,23 +110,6 @@ function Editor1Component() {
className="border"
/>
</div>
{status === 'blocked' && (
<div className="mt-2">
<div>Are you sure you want to leave editor 1?</div>
<button
className="bg-lime-500 text-white rounded p-1 px-2 mr-2"
onClick={proceed}
>
YES
</button>
<button
className="bg-red-500 text-white rounded p-1 px-2"
onClick={reset}
>
NO
</button>
</div>
)}
<hr className="m-2" />
<Link to="/editor-1/editor-2">Go to Editor 2</Link>
<Outlet />
Expand All @@ -126,11 +126,6 @@ const editor2Route = createRoute({
function Editor2Component() {
const [value, setValue] = React.useState('')

useBlocker({
blockerFn: () => window.confirm('Are you sure you want to leave editor 2?'),
condition: value,
})

return (
<div className="p-2">
<h3>Editor 2</h3>
Expand Down
1 change: 1 addition & 0 deletions packages/history/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export function createHistory(opts: {
},
push: (path: string, state: any) => {
state = assignKey(state)
console.log('TRY NAVIGATION')
tryNavigation({
task: () => {
opts.pushState(path, state)
Expand Down
79 changes: 46 additions & 33 deletions packages/react-router/src/useBlocker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,31 @@ import * as React from 'react'
import { useRouter } from './useRouter'
import type { BlockerFn, BlockerFnArgs } from '@tanstack/history'
import type { ReactNode } from './route'
import { RegisteredRouter, RouteIds, getRouteApi } from '.'

type BlockerResolver = {
status: 'idle' | 'blocked'
proceed: () => void
reset: () => void
}

type BlockerOpts = {
blockerFn?: BlockerFn
condition?: boolean | any
type BlockerOpts<TId extends RouteIds<RegisteredRouter['routeTree']>> = {
blockerFn: BlockerFn
from?: TId
to?: TId
disabled?: boolean
}

export function useBlocker(blockerFnOrOpts?: BlockerOpts): BlockerResolver

/**
* @deprecated Use the BlockerOpts object syntax instead
*/
export function useBlocker(
blockerFn?: BlockerFn,
condition?: boolean | any,
): BlockerResolver

export function useBlocker(
blockerFnOrOpts?: BlockerFn | BlockerOpts,
condition?: boolean | any,
): BlockerResolver {
const { blockerFn, blockerCondition } = blockerFnOrOpts
? typeof blockerFnOrOpts === 'function'
? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true }
: {
blockerFn: blockerFnOrOpts.blockerFn,
blockerCondition: blockerFnOrOpts.condition ?? true,
}
: { blockerFn: undefined, blockerCondition: condition ?? true }
const { history } = useRouter()
export function useBlocker<
TId extends RouteIds<RegisteredRouter['routeTree']>,
>({
blockerFn,
from,
to,
disabled = false,
}: BlockerOpts<TId>): BlockerResolver {
const router = useRouter()
const { history } = router

const [resolver, setResolver] = React.useState<BlockerResolver>({
status: 'idle',
Expand All @@ -48,9 +38,24 @@ export function useBlocker(
const blockerFnComposed = async (blockerFnArgs: BlockerFnArgs) => {
// If a function is provided, it takes precedence over the promise blocker

let matchesFrom = true
let matchesTo = true

if (from) {
const route = getRouteApi(blockerFnArgs.currentLocation.pathname)
if (route.id !== from) matchesFrom = false
}

if (to) {
const route = getRouteApi(blockerFnArgs.nextLocation.pathname)
if (route.id !== to) matchesTo = false
}

if (!matchesFrom || !matchesTo) return true

if (blockerFn) {
const shouldBlock = await blockerFn(blockerFnArgs)
if (shouldBlock) return false
if (!shouldBlock) return true
}

const promise = new Promise<boolean>((resolve) => {
Expand All @@ -72,14 +77,20 @@ export function useBlocker(
return canNavigateAsync
}

return !blockerCondition ? undefined : history.block(blockerFnComposed)
}, [blockerFn, blockerCondition, history])
return disabled ? undefined : history.block(blockerFnComposed)
}, [blockerFn, disabled, history])

return resolver
}

export function Block({ blockerFn, condition, children }: PromptProps) {
const resolver = useBlocker({ blockerFn, condition })
export function Block({
blockerFn,
from,
to,
disabled = false,
children,
}: PromptProps) {
const resolver = useBlocker({ blockerFn, disabled, from, to })
return children
? typeof children === 'function'
? children(resolver)
Expand All @@ -88,7 +99,9 @@ export function Block({ blockerFn, condition, children }: PromptProps) {
}

export type PromptProps = {
blockerFn?: BlockerFn
condition?: boolean | any
blockerFn: BlockerFn
from: string
to: string
disabled?: boolean
children?: ReactNode | (({ proceed, reset }: BlockerResolver) => ReactNode)
}

0 comments on commit 0550af2

Please sign in to comment.