reactjs - Tidak dapat menggunakan useNavigate di hook custom dengan createBrowserRouter
Saya memiliki hook kustom ini untuk login dan logout
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
}
}
Dan saya ingin memanfaatkan router data baru dengan react-router-dom createBrowserRouter dan mencoba membuat breadcrumb. Sebelumnya saya menggunakan <BrowserRouter> dan itu bekerja dengan baik, tetapi dengan pendekatan baru ini, dan karena penggunaan protected routes, saya mendapatkan error.
useNavigate() hanya dapat digunakan dalam konteks sebuah komponen.
Saya sepenuhnya memahami kesalahan itu, tetapi saya tidak tahu bagaimana cara membuatnya bekerja. Ini terkait dengan masalah lain yang saya posting bersama masalah ini dan dengan masalah serupa. Berikut adalah kode saya:
CommonLayout.tsx and AuthLayout.tsxexport 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 }} />
)
}
Dan pendekatan baru yang saya inginkan:
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} />
}
Seperti yang Anda lihat, saya membutuhkan user karena beberapa rute membutuhkannya, tetapi mengimpor hook menimbulkan kesalahan. Bagaimana saya bisa menghindarinya?
Pendekatan pertama adalah menghapus useNavigate dari hook, useUser.
Yang kedua adalah memodifikasi tata letak, sehingga tata letak mengimpor hooks dan menerima sebagai parameter sesuatu seperti izin (dalam kasus izin) dan kondisi (dalam kasus saya membutuhkannya)
Terima kasih sebelumnya
Jawaban #1
Hook useUser hanya dapat dipanggil dalam konteks routing yang disediakan oleh router, jadi perlu dipindahkan.
Yang kedua adalah memodifikasi tata letak, sehingga tata letak mengimpor hooks dan menerima sebagai parameter sesuatu seperti izin (dalam kasus izin) dan kondisi (dalam kasus saya membutuhkannya).
Ini adalah metode yang saya sarankan. Hook useUser sudah membaca apa yang saya asumsikan sebagai satu nilai AuthContext global, jadi dari yang saya lihat sepenuhnya aman untuk menyalurkan panggilan hook useUser ke bawah ReactTree ke komponen yang membutuhkannya.
Perbarui komponen tata letak untuk mengakses nilai useUser hook dan kirimkan data statis sebagai 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 }} />
);
}
Perbarui komponen rute tata letak sesuai. CommonLayout sekarang tidak menggunakan props apa pun, dan AuthLayout diberikan array peran/izin. Panggilan hook useUser dihapus.
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} />;
}
