typescript 无法在自定义钩子中使用createBrowserRouter的useNavigate

stszievb  于 2023-06-07  发布在  TypeScript
关注(0)|答案(1)|浏览(165)

我有这个自定义挂钩登录和注销

export function useUser() {
    const { token, setToken, user, setUser } = useContext(AuthContext)
    const navigate = useNavigate()

    const {
        mutate: login,
        isLoading: isLoginLoading,
        isError: hasLoginError,
        
    } = useMutation({
        mutationFn: signIn,
        onSuccess: async (tokenResponse) => {
            sessionStorage.setItem('access_token', tokenResponse.access_token)
            sessionStorage.setItem('refresh_token', tokenResponse.refresh_token)
            setToken(tokenResponse.access_token)
            const user = await getUserProfile()
            setUser(user)
            sessionStorage.setItem('userData', JSON.stringify(user))
            navigate('/home')
        },
        onError: () => {
            sessionStorage.removeItem('access_token')
        }
    })

    const { mutate: logout } = useMutation({
        mutationFn: signOut,
        onMutate: () => {
            sessionStorage.clear()
            setToken(null)
            setUser(null)
        },
        onSuccess: async () => {
            navigate('/login')
        }
    })

    return {
        login,
        logout,     
        isLoginLoading,
        hasLoginError,
        userPermissions: token ? (jwt_decode(token) as TokenDecoded).authorities : [],
        user
    }
}

我想利用新的数据路由器react-router-dom createBrowserRouter,并尝试创建一个breadcrumb。在此之前,我使用<BrowserRouter>,它工作得很好,但是使用这种新方法,并且由于使用了受保护的路由,我得到了错误。
useNavigate()只能在组件的上下文中使用。
我完全理解这个错误,但我不知道如何使它工作。这与我发布的另一个问题this issue和一个类似的问题similar issue有关。下面是我的代码:
CommonLayout.tsx and AuthLayout.tsx

export const CommonLayout = ({ isAllowed }: Props) => {
    return (
        <div className='flex flex-col h-screen'>
            {isAllowed ? <Navbar /> : <GuestNavbar />}
            <main className='flex-1 min-h-max p-8 bg-slate-100 dark:bg-neutral-800'>
                <Outlet />
            </main>
            <Footer />
        </div>
    )
}

export const AuthLayout = ({ isAllowed, redirectPath, children }: Props) => {
    const location = useLocation()

    return isAllowed ? (
        children ?? <Outlet />
    ) : (
        <Navigate to={redirectPath} replace state={{ from: location }} />
    )
}

我想要的新方法是:

export function App() {
    const { user, userPermissions } = useUser()
    const router = createBrowserRouter(
        createRoutesFromElements(
            <>
                <Route element={<CommonLayout isAllowed={!!user} />}>
                    <Route path='login' element={<Login />} />
                    <Route element={<AuthLayout isAllowed={userPermissions.includes('PERMISSION_1')} redirectPath='/login' />}>
                        <Route
                            path='home'
                            element={
                                <>
                                    <Breadcrumbs />
                                    <h1>Home</h1>
                                </>
                            }
                            handle={{ crumb: () => 'Home' }}
                        />
                        <Route
                            path='tracking'
                            element={
                                <Suspense fallback={<>...</>}>
                                    <Breadcrumbs />

                                    <Tracking />
                                </Suspense>
                            }
                            handle={{ crumb: () => 'Tracking' }}
                        />
                        <Route
                            path='cleaning'
                            element={
                                <Suspense fallback={<>...</>}>
                                    <Breadcrumbs />

                                    <Cleaning />
                                </Suspense>
                            }
                        />
                        <Route
                            path='admin'
                            element={
                                <Suspense fallback={<>...</>}>
                                    <Breadcrumbs />
                                    <Admin />
                                </Suspense>
                            }
                            handle={{ crumb: () => 'Admin' }}
                        />
                    </Route>
                </Route>
                <Route path='*' element={<Navigate to='/home' replace />} />
            </>
        )
    )
    return <RouterProvider router={router} />
}

如您所见,我需要user,因为有些路由需要它,但是导入钩子会抛出错误。我该如何避免这种情况?
第一种方法是从钩子useUser中删除useNavigate
第二个是修改布局,这样布局就可以导入钩子,并作为参数接收权限(如果是权限)和条件(如果我需要)
先谢谢你了

xytpbqjk

xytpbqjk1#

useUser钩子只能在路由器提供的路由上下文中调用,因此需要移动它。
第二个是修改布局,这样布局就可以导入钩子,并作为参数接收权限(在权限的情况下)和条件(在我需要的情况下)。
这是我推荐的方法。useUser钩子已经阅读了我假设的一个全局AuthContext值,所以从我所看到的来看,将useUser钩子调用向下推送到ReactTree中需要它的组件是完全安全的。
更新布局组件以访问useUser钩子值并将静态数据作为props传入。

export const CommonLayout = () => {
  const { user } = useUser();

  return (
    <div className='flex flex-col h-screen'>
      {!!user ? <Navbar /> : <GuestNavbar />}
      <main className='flex-1 min-h-max p-8 bg-slate-100 dark:bg-neutral-800'>
        <Outlet />
      </main>
      <Footer />
    </div>
  )
}
export const AuthLayout = ({ children, redirectPath, roles = [] }: Props) => {
  const location = useLocation();
  const { userPermissions } = useUser();

  const isAllowed = userPermissions.some(permission => roles.includes(permission));

  return isAllowed ? (
    children ?? <Outlet />
  ) : (
    <Navigate to={redirectPath} replace state={{ from: location }} />
  );
}

相应地更新布局管线元件。CommonLayout现在不使用任何props,并且AuthLayout被传递了一个角色/权限数组。删除useUser钩子调用。

export function App() {
  const router = createBrowserRouter(
    createRoutesFromElements(
      <>
        <Route element={<CommonLayout />}>
          <Route path='login' element={<Login />} />
          <Route
            element={(
              <AuthLayout roles={['PERMISSION_1']} redirectPath='/login' />
            )}
          >
            <Route
              path='home'
              element={
                <>
                  <Breadcrumbs />
                  <h1>Home</h1>
                </>
              }
              handle={{ crumb: () => 'Home' }}
            />
            <Route
              path='tracking'
              element={
                <Suspense fallback={<>...</>}>
                  <Breadcrumbs />
                  <Tracking />
                </Suspense>
              }
              handle={{ crumb: () => 'Tracking' }}
            />
            <Route
              path='cleaning'
              element={
                <Suspense fallback={<>...</>}>
                  <Breadcrumbs />
                  <Cleaning />
                </Suspense>
              }
            />
            <Route
              path='admin'
              element={
                <Suspense fallback={<>...</>}>
                  <Breadcrumbs />
                  <Admin />
                </Suspense>
              }
              handle={{ crumb: () => 'Admin' }}
            />
          </Route>
        </Route>
        <Route path='*' element={<Navigate to='/home' replace />} />
      </>
    )
  );

  return <RouterProvider router={router} />;
}

相关问题