I am working with react-router-dom v6.8.1 (newest version as of now), and previously had a working breadcrumb setup using this third-party lib called use-react-router-breadcrumbs, but according to it's doc, they are now instead recommending doing it the "built-in react-router way", that is documented here. It's based on attaching a crumb to the handle object of each route, and retrieve it using the useMatches hook.
So I rewrote the code, but it has a quite major flaw that I cannot get around. Say that I have 3 routes, where 2 and 3 is nested below 1:
{
path: '/',
element: <Layout />,
handle: {
crumb: () => 'Home',
},
children: [
{
path: '/users',
element: <UserList />,
handle: {
crumb: () => 'Users',
},
},
{
path: '/users/:id',
element: <UserDetails />,
handle: {
crumb: () => <DynamicUserNameCrumb />,
},
},
]
}
With the custom lib you can go to /users/:id and get a breadcrumb for each one of these routes, making the entire breadcrumbs look like:
"Home -> Users -> John Doe"
However, when using the new built-in way with the useMatches() hook, I only get a match on route 1 and 3. Route 2 (/users) is not considered a match, and I cannot access the crumb for that route. Result is this, which is not what I want:
"Home -> John Doe"
So my question is: How are you supposed to handle this kind of situation? Nesting route 3 under 2 was my first idea, and this made the crumbs correct, but then it actually renders the component defined for route 2 (User list), and I only want it to render route 1 (layout) and 3 (User details page).
I was hoping that maybe useMatches() would be able to accept configuration for also returning partial matches, but it seems that this hook does not accept any input.
I am close to reverting and going back to the third party lib, but wanted to ask here before I do so, since they explicitly recommended using the native solution based on useMatches and a handle object. I figured there must be a solution for this if this is the officially recommended way to handle breadcrumbs in react-router
From what I can tell it is because "/users" and "/users/:id" are sibling routes. Refactor the routes config such that "/users/:id" is a child route of "/users" so there's a "logical path" and individual segments from "/" to "users" to ":id".
Example:
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
handle: {
crumb: () => "Home"
},
children: [
{
path: "/users",
handle: {
crumb: () => "Users"
},
children: [
{
index: true,
element: <UserList />
},
{
path: "/users/:id",
element: <UserDetails />,
handle: {
crumb: () => <DynamicUserNameCrumb />
}
}
]
}
]
}
]);
Related
I have the following structure of the code:
router.js:
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'App',
component: () => import('../../src/App.vue'),
},
{
path: '/:pathMatch(.*)',
name: 'ErrorView',
component: () => import('../components/Error.vue'),
}
],
});
export default router;
App.vue
<template>
<Land />
<LeftNavbar />
<Navbar />
<Experience />
<TechnologiesCarousel />
<Projects />
<Copyright />
<BackToTop />
</template>
When I'm pressing in the URL bar: http://localhost:3000. The app is rendering properly which is fine, but when I'm trying to write a wrong URL, for eg: http://localhost:3000/abcf/ || http://localhost:3000/dsafbdmhgfjweghjfw to be redirected to the 404 page, I'm not redirected, the page still rendering the App.vue component.
Does anyone have any idea why the 404 page isn't rendered?
Try like following /:pathMatch(.*)*:
{
path: "/:pathMatch(.*)*",
name: 'ErrorView',
component: () => import('../components/Error.vue'),
},
and in App.vue place <router-view /> and move components to other view.
I assume you are using Vue 3.
In your router file you need to change the path to this:
{
path: '/:pathMatch(.*)*',
name: 'ErrorView',
component: () => import('../components/Error.vue'),
}
For Vue 2 the path can be: path:'*'
From Vue 2 -> 3 migration some more info for those they want to know what has changed:
Essentially, you should be able to replace the '' path with '/:pathMatch(.)*' and be good to go!
Reason: Vue Router doesn't use path-to-regexp anymore, instead it implements its own parsing system that allows route ranking and enables dynamic routing. Since we usually add one single catch-all route per project, there is no big benefit in supporting a special syntax for *.
I want to have a couple of "overview" pages for sections my app, that will all be triggered on the root of that section.
so localhost/hi should display component HiOverview
localhost/he should display component HeOverview
as there are multiple of those, i want to avoid assigning the component to a const, then reusing it in a route. instead i want to handle all that in a single dynamic route.
BUT i'm struggling with the creation of the Components in the beforeEnter hook.
each route object expects a component... but i just want to decide the component depending on route. (sectionsWithOverview is a simple array of strings containing the names of routes where i want an overview displayed
const router = new Router({
linkActiveClass: 'active',
mode: 'history',
routes: [
{ path: '/:section',
component: Placeholder,
beforeEnter: (to, from, next) => {
const section = to.params.section
// how can i use this in the next() call?
// const View = () => import(/* webpackChunkName: 'sectionView' */ `Components/${section}/${section}Overview`)
if (sectionsWithOverview.includes(to.params.section)) {
next({ name: `${to.params.section}Overview` })
} else {
next()
}
},
}
can you guys help me? how can i conditionally assign a component onBeforeEnter, and then route to that exact Component?
it works if i declare each SectionOverview beforehand, but that makes my whole idea pointless.
Thanks for any help :)
I made something similar with a project but instead I used beforeRouteUpdate
Here is an example of how it works. On route.js simply define your dynamic route
const router = new Router({
linkActiveClass: 'active',
mode: 'history',
routes: [
{
path: '/:section',
component: Placeholder,
name: 'placeholder'
},
}
Then in your component (I assume Placeholder.vue) in your HTML add this line of code
<transition name="fade" mode="out-in">
<component :is="section" key="section"></component>
</transition>
then in your JS add the beforeRouteUpdate hook and define all your components that will match your route section param.
import he from './heOverview.vue'
import hi from './hiOverview.vue'
beforeRouteUpdate (to, from, next) {
// just use `this`
this.section = to.params.section
next()
},
components: {
he,
hi
},
data () {
return {
section: ''
}
}
So when a user navigate to localhost/he the heOverview.vue component will be loaded. The only thing you have to make sure is that the section param's value should match an specific view if not an error will be produced
If you need more info about how this work, read
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
So ive been looking into vue and been experiencing an issue which i cant seem to find the solution for. Im using Vue and Vue-router. I started with the basic vue + webpack template which gave the initial boilerplate.
I've successfully added additional routes to the predefined routes which is working as expected (games, tournaments, stats and users routes works just fine). However now im unable to get additional routes to work. the "gaming" route doesnt work, ive also tried adding additional routes which does not seem to work either.
So this is my current router file (index.js):
import Vue from 'vue'
import Router from 'vue-router'
const Gaming = () => import('#/components/gaming.vue');
const Home = () => import('#/components/home.vue');
const Game = () => import('#/components/game.vue');
const Tournament = () => import('#/components/tournament.vue');
const Users = () => import('#/components/users.vue');
const Stats = () => import('#/components/stats.vue');
const router = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/games',
name: 'Game',
component: Game,
},
{
path: '/wtf',
name: 'Gaming',
components: Gaming,
},
{
path: '/tournaments',
name: 'Tournament',
component: Tournament
},
{
path: '/users',
name: 'Users',
component: Users
},
{
path: '/stats',
name: 'Stats',
component: Stats
}
]
});
export default router;
Vue.use(Router);
All my routes works as expected except the "Gaming" route. The "Gaming" component looks like this:
<template>
<div>
<h1>WTF?!?!?!?!?=!?!</h1>
</div>
</template>
<script>
export default {
name: 'Gaming',
components: {},
data() {
return {}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
</style>
Ive tried to pretty much copy/paste a working component, And only change the name, as well as the template. But it seems to have issues. Initially i had done the route component imports the normal "boilerplate" way
import Stats from '#/components/Stats'
Which pretty much had the same result, Except this would cause an exception when attempting to navigate to the "gaming" route.
Cannot read property '$createElement' of undefined
at render (eval at ./node_modules/vue-loader/lib/template-compiler/index.js?{"id":"data-v-c9036282","hasScoped":false,"transformToRequire":{"video":["src","poster"],"source":"src","img":"src","image":"xlink:href"},"buble":{"transforms":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/components/gaming.vue (app.js:4240), <anonymous>:3:16)
All other routes worked. Ive also tried to re-create all the files and re-do the specific route which doesnt seem to work either. So im at a loss of what i can do to fix this issue?
Here i attempt to inspect the route, And as you can see the component is missing "everything"
Inspecting the route
Also tried looking with the vue addon for chrome, Where the component does not get loaded into the view
Vue Chrome Extension
Uploaded the project to gdrive if someone want to tweak around with it
Google Drive Link
Found the issue:
const router = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/games',
name: 'Game',
component: Game,
},
{
path: '/wtf',
name: 'Gaming',
components <----------: Gaming,
// Should be component not componentS
},
{
path: '/tournaments',
name: 'Tournament',
component: Tournament
},
...
Also, you should use the standard method of importing. Without that error, I would've never found the issue.
Is there a way to define a dynamic route with fixed set of values? And if it doesn't fit any of the fixed values it would fallback to the next route. My current is like this -
const routes = {
path: '',
component: AppComponent,
childRoutes: [
{ path: '/search/top', name: 'top', component: FixedSearchComponent},
{ path: '/search/new', name: 'new', component: FixedSearchComponent},
{ path: '/search', name: 'search', component: SearchComponent},
{ path: '/search/:query', name: 'search', component: SearchComponent},
]
}
But I'd like to define a parameter for it like :fixedSearch maybe and have it predefined to only be this values. Maybe something like this? And if it doesn't fit any of top or new (or other possible set), it would fallback to search.
const routes = {
path: '',
component: AppComponent,
childRoutes: [
{ path: '/search/:fixedSearch', name: 'fixedSearch', fixedSearch: ['top', 'new'], component: FixedSearchComponent},
{ path: '/search', name: 'search', component: SearchComponent},
{ path: '/search/:query', name: 'search', component: SearchComponent},
]
}
You could attach an onEnter function a new route, like /fixed/:fixedSearch that replaces /search/new and /search/top. Inside the onEnter function you would compare :fixedSearch to your predefined values (['top', 'new']) and if they don't match you can fallback to your /search route. This is common for checking if a user is authenticated before allowing access to a route.
Here's the documentation for onEnter:
onEnter(nextState, replace, callback?)
Called when a route is about to be entered. It provides the next
router state and a function to redirect to another path. this will be
the route instance that triggered the hook.
If callback is listed as a 3rd argument, this hook will run
asynchronously, and the transition will block until callback is
called.
A rough example (in JSX) might look like:
<Route path='/fixed/:fixedSearch' component={FixedSearchComponent} onEnter={checkFixedSearch} />
function checkFixedSearch(nextState, replace) {
if (*Compare :fixedSearch with predefined values*) {
replace('/search') // move to search route if fixed values don't match
}
}
Suppose there is a component - UsersComponent, and it has two methods: getAlUsers() and getUser(id). In the #RouteConfig we can use only one name of the component (constructor will be called default ), but we can not specify what kind of a class method must be called. Is it possible to define something like this:
{
path: '/users/',
component: UsersComponent,
name: 'Users'
},
{
path: '/users/getAllUsers',
component: UsersComponent.getAllUsers,
name: 'GetAllUsers'
},
{
path: '/users/getUser',
component: UsersComponent.getUser,
name: 'GetUsers'
}
Or is it impossible to make means Angular 2?
This is a pretty common use case for component nesting and using sub components for the different actions like "user list" and "user details". The idea is to use pretty fine grained components with a very focused use case. If you have common code (e.g. loading data via http), consider moving this logic into a service that is used by all components.
You can achieve this by referencing a "UserComponent" in your top level Component routing with the "..." notation. Then inside this "UserComponent" define another routing with two configs for / and /:id such that you reference two child components "user list" and "user details".
Parent Route Config:
{
path: '/users/...',
name: 'Users',
component: UserComponent
}
Route Config in the UserComponent:
#RouteConfig([
{ path: "/", name: "User List", component: UserListComponent, useAsDefault: true },
{ path: "/:id", name: "User Detail", component: UserDetailComponent },
])