Route Parameters
In this guide, you’ll learn how to create a view that accepts a single route parameter. You’ll also explore the differences between required, optional, and wildcard route parameters.
Required Route Parameters
To create a Hilla view that accepts a required route parameter, you need to define the parameter name as the view name placed between curly braces ({}
) followed by the .tsx
suffix. For example, to create a view that accepts a required productId
parameter, you should name the view {productId}.tsx
. The directory structure under the views
looks like this:
views/
└── products/
└── {productId}.tsx (1)
-
The view file that accepts the
productId
parameter. The route is in the form of/products/{productId}
, whereproductId
is a required parameter. Routes such as/products/123
,/products/prd222
, etc., match this view. Trying to navigate to/products
results in a 404 Not Found error unless a view matching the route is defined.
Though it is technically possible to define the views with parameters directly under the views
directory, it is recommended to use subdirectories to keep the views organized.
The routing system also supports having parameters in the middle of the route, which means the directories can also have their names defined with a {param-name}
convention. Here is an example of a view that accepts a required productId
parameter:
views/
└── products
└── {productId} (1)
└── edit.tsx
-
The directory where all its children files have the
productId
parameter in the middle of their routes. The route is in the form of/products/{productId}/edit
, whereproductId
acts as a placeholder for the required parameter, in this case, the product ID. Routes such as/products/123/edit
,/products/prd222/edit
, etc., match this view. Trying to navigate to/products/edit
results in a 404 Not Found error unless a view matching the route is defined.
Accessing Route Parameter Value
To access the route parameter in the view, use the useParams
hook from react-router
in your component. Here is an example of a view that accepts a required productId
parameter:
import { useParams } from 'react-router';
export default function ProductView() {
const { productId } = useParams(); (1)
return (
<>
<h1>Product Details</h1>
<p>Product ID: {productId}</p>
</>
);
}
-
The
useParams
hook returns an object containing the route parameters. In this case, theproductId
parameter is extracted from the object and stored in a variable.
Passing Route Parameter
To navigate to a view that accepts a route parameter, use the NavLink
component from react-router
. Here is an example of how to create a link to a view located at /products/123
:
import { NavLink } from 'react-router';
...
<NavLink to="/products/123">Product 123</NavLink>
Another way is to use the useNavigate
hook to navigate programmatically. Here is an example of how to use useNavigate
for programmatic navigation:
import { useNavigate } from 'react-router';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/products/123');
};
return (
<button onClick={handleClick}>
Go to Product 123
</button>
);
}
By using either of the above methods, you can navigate to the /views/products/{productId}.tsx
view that accepts the productId
route parameter. If you navigate to /products/123
, the productId
parameter is extracted and used in the view to fetch the product details for the given ID.
If a parameter is expected in the middle of the route, the method for navigating to the view is the same. For example, to navigate to /products/123/edit
, use the same methods as above. The useParams
hook extracts the productId
parameter from the route, and the view fetches the product details for the given ID.
Optional Route Parameters
Views can also have optional route parameters. This means that the view can be navigated to with or without the parameter. To make a route parameter optional, use double curly braces around the parameter name when naming the view file. For example, to create a view that accepts an optional categoryName
parameter, name the view {{categoryName}}.tsx
. The directory structure under the views
looks like this:
views/
└── products
└── {{categoryName}}.tsx (1)
-
The view file that accepts the
categoryName
optional parameter. The route for this view is in the form of/products/{{categoryName}}
, wherecategoryName
is an optional parameter. Routes such as/products
,/products/electronics
,/products/clothing
, etc., match this view. Note that a view with an optional parameter should be able to handle both cases when the parameter is present and when it is not. Here is an example of a view that accepts an optionalcategoryName
parameter:
import { useParams } from "react-router";
import { ProductService } from "Frontend/generated/endpoints.js";
import { useEffect } from "react";
import { useSignal } from "@vaadin/hilla-react-signals";
export default function ProductByCategoriesView() {
const { categoryName } = useParams(); (1)
const products = useSignal<string[]>([]);
useEffect(() => {
if (categoryName == undefined) { (2)
ProductService.allProducts().then((data) => products.value = data);
} else {
ProductService.productsInCategory(categoryName).then((data) => products.value = data);
}
}, []);
return (
<>
<h3>Products from {categoryName ? `'${categoryName}' category` : "all categories"}:</h3>
<div>
<ul>{products.value.map((product) => (
<li key={product}>{product}</li>
))}</ul>
</div>
</>
);
}
-
The
useParams
hook returns an object containing the route parameters. In this case, thecategoryName
parameter is extracted from the object and stored in a variable. -
The
categoryName
parameter is checked to determine whether it is present or not. If it is not present, all products are fetched. Otherwise, products in the specified category are fetched.
In the above example, the ProductByCategoriesView
fetches all products when the categoryName
parameter is not present. When the categoryName
parameter is present, it fetches the products in the specified category. The view displays the products in the specified category or all products if the categoryName
parameter is not present.
Wildcard Route Parameters
Wildcard route parameters are used to match any number of URL segments. This means when a URL cannot be matched with the other defined routes, the wildcard route is picked as the fallback to handle the navigation. One of the common cases of defining wildcard route parameters when defining the routes in a Hilla application is to properly handle the navigation of users when the route is not found. As the default way of defining the routes is through defining the view files and proper directory structure, adding wildcard route parameters should be done by defining the file name as {…wildcard}.tsx
. The literal value wildcard
can be anything that is supported by the filesystem as the filename, but Vaadin recommends to use the {…wildcard}.tsx
as a conventional standard to make it more readable and intuitive. An example of a view that accepts a wildcard route parameter is shown below:
views/
├── @index.tsx
├── about.tsx
├── contact-us.tsx
└── {...wildcard}.tsx (1)
-
The view file that accepts the wildcard route parameter.
The routes that are matched with this view depends on the other defined routes. In this case the /
, /about
, and /contact-us
are mapped to their respective views, and if the user tries to navigate to any other routes such as /123
, /orders
, or even /about/789
, then the {…wildcard}.tsx
is matched as the fallback, accepting the whole unmatched segment of the URL as the wildcard parameter. The view can then handle the navigation and display a custom 404 Not Found page or redirect the user to the home page. Here is an example of a view that accepts a wildcard route parameter:
import { NavLink, useParams } from "react-router";
export default function WildcardView() {
const wildcard = useParams()['*']; (1)
return (
<>
<h3>Page Not Found!</h3>
<div>
The '<b>/{wildcard}</b>' route does not exist.
Go back to the <NavLink to="/">home page</NavLink>.
</div>
</>
);
}
-
The wildcard route parameter can be extracted using the
useParams
hook, which is stored in theparams
object with the(asterisk) as the key. The matched wildcard parameters can have many segments, and all the segments is extracted at once when reading the
params
object with the(asterisk) as the key.