2. Defining urls
Lets look at one of the main parts of @42.nl/react-url
; urlBuilder
. It fixes the string based route
problem via one simple
abstraction: URL functions. URL functions are functions which return URLs.
Optionally these URL functions take query and path parameters
as arguments.
A function is a URL function if it matches the following criteria:
- The function has a return type of
Url
. - If the Url has path parameter, they are parameters to the url function.
- If the Url has query parameters, they are a parameter to the url function.
The benefit of using functions to navigate to routes is type safety. TypeScript cannot differentiate between strings, but it can type check functions. So a change to the url function will give you compiler errors, which is exactly what we want!
Let’s take a look at some examples:
2.1 Basic: no parameters at all
The easiest routes to define are routes which do not have any parameters at all. The URL function is one that simply returns a string:
import { Url } from '@42.nl/react-url';
/* This const should be used to define the `Route` and in the url function */
export const USER_CREATE_URL = '/users/create';
/* The simplest way to define a Url function is to return a string. */
export function toUserCreate(): Url {
return USER_CREATE_URL;
}
/* To use it in react-router use the URL const */
<Route path={USER_CREATE_URL} component={UserCreate}/>
/* To use it to navigate to a route you can use it in a react router Link: */
<Link to={toUserCreate()}>/users/create</Link>
/* Or programmatically -> /users/create */
history.push(toUserCreate());
It is very important to define a const
for the URL
and to use
that const
when creating the Route
. This ensures type safety.
2.2 With path parameters
If the route has path parameters:
import { Url, urlBuilder } from '@42.nl/react-url';
/* We recommend defining this type in the UserEdit component's file. */
type UserEditPathParams = {
id: string; /* All path params are strings. */
}
/* This const should be used to define the `Route` and in the url function */
export const USER_EDIT_URL = '/users/:id/edit';
/* This url function takes a required path parameter called :id */
export function toUserEdit(pathParams: UserEditPathParams): Url {
return urlBuilder({
url: USER_EDIT_URL,
pathParams
});
}
/* To use it in react-router use the URL const */
<Route path={USER_EDIT_URL} component={UserEdit} />
/* To use it to navigate to a route you can use it in a react-router Link: */
<Link to={toUserEdit({ id: '42' })}>/users/42/edit</Link>
/* Or programmatically -> /users/42/edit */
history.push(toUserEdit({ id: '42'}));
It is important that the argument pathParams
to the function toUserEdit
is required
. Once again, this ensures type safety.
2.3 With optional path params
If the route has optional path params. Optional path params are not required for the route to activate. You can combine
import { Url, urlBuilder } from '@42.nl/react-url';
/* We recommend defining this type in the UserList component's file. */
type UserListPathParams = {
tab?: 'all' | 'inactive' | 'active'; /* All path params are strings. */
}
/* This const should be used to define the `Route` */
export const USER_LIST_URL = '/users/:action?';
export function toUserList(
pathParams?: UserListPathParams, // The path params can be optional now
): Url {
return urlBuilder({
url: USER_LIST_URL,
pathParams
});
}
/* To use it in react-router use the URL const */
<Route path={USER_LIST_URL} component={UserList} />
/* To use it to navigate to a route you can use it in a react-router Link: */
<Link to={toUserList()}>/users</Link>
/* The tab is optional but can be provided */
<Link to={toUserList({ tab: 'all'})}>/users/all</Link>
/* Or programmatically -> users/active */
history.push(toUserEdit({tab: 'active'}));
2.4 With required path params and optional path params
The route can also have both required path params and optional path params.
import { Url, urlBuilder } from '@42.nl/react-url';
/* We recommend defining this type in the UserList component's file. */
type UserDetailPathParams = {
id: string; /* All path params are strings. */
mode?: 'view' | 'edit'; /* All path params are strings. */
}
/* This const should be used to define the `Route` */
export const USER_DETAIL_URL = '/users/:id/:action?';
export function toUserDetail(
pathParams: UserDetailPathParams,
): Url {
return urlBuilder({
url: USER_DETAIL_URL,
pathParams
});
}
/* To use it in react-router use the URL const */
<Route path={USER_DETAIL_URL} component={UserList} />
/* To use it to navigate to a route you can use it in a react-router Link: */
<Link to={toUserDetail({ id: '42'})}>/users/42</Link>
/* The mode is optional but can be provided */
<Link to={toUserDetail({ id: '42', mode: 'view'})}>/users/42/view</Link>
/* Or programmatically -> users/42/edit */
history.push(toUserEdit({ id: '42', mode: 'edit'}));
2.5 With query params
If the route has query params:
import { Url, urlBuilder } from '@42.nl/react-url';
/* We recommend defining this type in the UserList component's file. */
type UserListQueryParams = {
page: number;
search: string;
}
/* We recommend defining the default query params in the UserList component's file. */
export function defaultUserListQueryParams(): UserListQueryParams {
return {
page: 1,
search: ''
}
}
/* This const should be used to define the `Route` and in the url function */
export const USERS_URL = '/users';
/*
This url function has the query params page and search. They are both
optional because of Partial. Partial takes an object type definition
and makes all keys optional and does not allow unknown keys.
*/
export function toUsers(queryParams?: Partial<UserListQueryParams>): Url {
return urlBuilder({
url: USERS_URL,
queryParams,
defaultQueryParams: defaultUserListQueryParams()
});
}
/* To use it in react-router use the URL const */
<Route path={USERS_URL} component={UserList} />
/* To use it to navigate to a route you can use it in a react-router Link: */
<Link to={toUsers({ page: 13 })}>/users?page=13</Link>
/* Or programmatically -> /users?page=13 */
history.push(toUsers({ page: 13 }));
It is important that the argument queryParams
to the toUsers
function is optional and marked as Partial
. This ensures that when no
query params are given the defaults are used. It is Partial
because
all keys are optional when navigating to a URL whereas missing properties
will be filled in by the defaultQueryParams
.
The query parameters are not appended to the URL when they precisely match the default query parameters. This ensures that the URLs are as compact as possible.
Here are some examples of this behavior:
toUsers({ page: 1, search: '' }); /* -> /users */
toUsers({ page: 42, search: '' }); /* -> /users?page=42 */
toUsers({ page: 1, search: 'answer' }); /* -> /users?answer=42 */
2.6 With path params and query params
If the route has both path parameters and query parameters:
import { Url, urlBuilder } from '@42.nl/react-url';
/* We recommend defining this type in the UserEdit component's file. */
type UserEditPathParams = {
id: string; /* All path params are strings. */
}
/* We recommend defining this type in the UserEdit component's file. */
type UserEditQueryParams = {
modal: boolean;
}
/* We recommend defining the default query params in the UserList component's file. */
export function defaultUserEditQueryParams(): UserEditQueryParams {
return {
modal: false
}
}
/* This const should be used to define the `Route` */
export const USER_EDIT_URL = '/users';
export function toUserEdit(
pathParams: UserEditPathParams,
queryParams?: Partial<UserEditQueryParams>
): Url {
return urlBuilder({
url: USER_EDIT_URL,
pathParams,
queryParams,
defaultQueryParams: defaultUserEditQueryParams()
});
}
/* To use it in react-router use the URL const */
<Route path={USER_EDIT_URL} component={UserEdit} />
/* To use it to navigate to a route you can use it in a react-router Link: */
<Link to={toUserEdit({id: '42'}, { modal: true })}>/users/42/edit?modal=true</Link>
/* Or programmatically -> users/42/edit?modal=true */
history.push(toUserEdit({id: '42'}, { modal: true }));
Note: you can also define routes with required path params, optional path params, and query params.
Now that we have setup URLs lets take a look at how query params work.