-
-
Notifications
You must be signed in to change notification settings - Fork 696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(react-router): update useBlocker
hook to provide current and next location when conditionally blocking
#1790
Conversation
we wanted to follow react-router's API for this: https://reactrouter.com/en/main/hooks/use-blocker can you adjust your PR to match it? also, the location should be typesafe instead of just a additionally, we would also need tests for |
Yes sounds good, I will update the PR as soon as possible |
This implementation should now be providing the Things left to do:
I am not quite sure how to do with the old Would also like to give a quick pitch for a different interface that could be used for import React from 'react'
import type { BlockerFn } from '../../lib/router/qudak-router'
import { BlockerFn, useRouter } from '@tanstack/react-router'
type BlockerOpts = {
blockerFn: BlockerFn
disabled?: boolean
}
export function useBlocker({ blockerFn, disabled = false }: BlockerOpts) {
const { history } = useRouter()
React.useEffect(() => {
return disabled ? undefined : history.block(blockerFn as BlockerFn)
}, [blockerFn, disabled, history])
} Which gives very clean implementations for custom UIs using hooks and could look something like this: useBlocker({
blockerFn: () => {
if (blockerDisabled && blockerDisabled.current) {
blockerDisabled.current = false
return false
}
if (!blockerDisabled && _blockerDisabled.current) {
_blockerDisabled.current = false
return false
}
const shouldBlock = props.blockerFn()
if (!shouldBlock) return false
const promise = new Promise<boolean>((resolve) => {
modals.open({
title: 'Are you sure you want to leave?',
children: (
<SaveBlocker
onSave={onSave}
onDiscard={onDiscard}
close={() => {
modals.closeAll()
resolve(false)
}}
confirm={() => {
modals.closeAll()
resolve(true)
}}
reject={() => {
modals.closeAll()
resolve(true)
}}
/>
),
onClose: () => resolve(false)
})
})
return promise
},
disabled
}) Any thoughts on this? |
blockerFn
useBlocker
hook to provide current and next location when conditionally blocking
This would be a breaking change to |
While this might be out of scope for this PR. I think we can likely do much better from a type safety point of view for I was imagining a more type safe hook. useBlocker({ from: Route.fullPath, to: '..', blockerFn: ({ fromMatch, toMatch }) => // have I saved? })
When |
we should make this as typesafe as possible, as per @chorobin's comment. |
@tomrehnstrom, are you planning to continue working on this PR? Does anyone know a quick and dirty workaround I can apply in the meanwhile? |
Yes, but have been quite busy elsewhere recently. The way i have solved this quickly in our codebase is just to copy the history package index.ts file and make the changes required to make it work, and then use it as a custom history: const history = createCustomBrowserHistory()
export const router = createRouter({
routeTree,
defaultPendingComponent: FullWidthLoader,
defaultErrorComponent: RouterSuspenseErrorFallback,
defaultNotFoundComponent: Error404,
context: {
auth: undefined!,
queryClient
},
defaultPreload: 'intent',
// Since we're using React Query, we don't want loader calls to ever be stale
// This will ensure that the loader is always called when the route is preloaded or visited
defaultPreloadStaleTime: 0,
history
}) With the changes in my first commits to this pr Then written a custom blocker hook like this: type BlockerOpts = {
blockerFn: CustomBlockerFn
disabled?: boolean
}
export function useCustomBlocker({ blockerFn, disabled = false }: BlockerOpts) {
const { history } = useRouter()
React.useEffect(() => {
return disabled ? undefined : history.block(blockerFn as BlockerFn)
}, [blockerFn, disabled, history])
} |
Thanks @tomrehnstrom, I needed this urgently and managed to make do with the suggested temporary solution for now. |
bc05f95
to
217c9c5
Compare
☁️ Nx Cloud ReportCI is running/has finished running commands for commit 2ee506f. As they complete they will appear below. Click to see the status, the terminal output, and the build insights. 📂 See all runs for this CI Pipeline Execution
✅ Successfully ran 1 targetSent with 💌 from NxCloud. |
For anyone stumbling across the PR and really wants this now, use my fork: https://github.com/tomrehnstrom/router/tree/better-blocker-router |
I need to have a closer look, what's the current status of this PR? anything obvious missing ? |
The type safety and type narrowing described by @chorobin is not really implemented, but functionally of it should be complete, have been using this method in prod for quite a while and haven't had any issues so far. Maybe the old interface for |
is your PR breaking the current |
import type { BlockerFnArgs } from '@tanstack/history' | ||
import type { UseBlockerOpts } from './useBlocker' | ||
|
||
export function usePromiseBlocker({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we have this? isn't useBlocker
enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the skipResolver
option to the normal useBlocker
instead, look at the example in the docs i added to see why this pattern is nice for using modals when asking for discarding changes which is a common usecase.
let's work on merging this. |
yes! |
useBlocker
hook to provide current and next location when conditionally blockinguseBlocker
hook to provide current and next location when conditionally blocking
I am working on making the API typesafe, stay tuned |
Cool! I think using setup will be much more useful than having |
Anything here I can help with here @schiller-manuel? |
we need
I have also added a TODO whether we should return current and next in case the resolver is used. I thought about this when editing the example since we only know the status then but don't know current/next which might be necessary for UI? |
Yes, I will jump on updating docs and supplying the current blocking state to the resolver. |
@tomrehnstrom can you ping me on discord please? |
function MyComponent() { | ||
const [formIsDirty, setFormIsDirty] = useState(false) | ||
|
||
const { proceed, reset, status } = useBlocker({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { proceed, reset, status } = useBlocker({ | |
useBlocker({ |
since withResolver
is not true
, this won't return the (unused) props
@@ -1,42 +1,425 @@ | |||
import React from 'react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also need some tests that use the <Block>
component
Currently, when using the
useBlocker
hook there is no way to ascertain where the navigation being blocked is headed.For my usage of blocking, this is necessary to allow navigation within a selected bunch of nested routes, but not allowing the user to exit the base route. Thought this could be a useful feature to have.