Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A util for creating route paths. It would be great to add types to this.
import invariant from 'invariant'
import { matchPath } from 'react-router'
import { pathToRegexp, compile } from 'path-to-regexp'
import map from 'lodash/map'
import trim from 'lodash/trim'
import flow from 'lodash/flow'
import partialRight from 'lodash/partialRight'
import isString from 'lodash/isString'
import isEmpty from 'lodash/isEmpty'
import { createSymbol } from '../../utils'
export const PATH = createSymbol('route-path')
export const PARAMS = createSymbol('route-params')
export const isValidPathOrRoute = (input) => {
const isValidPath = isString(input) && !isEmpty(trim(input))
const isRoute = input instanceof Route
return isValidPath || isRoute
}
export const resolvePath = (pathOrRoute) =>
pathOrRoute instanceof Route ? pathOrRoute.path : pathOrRoute
// Remove leading and trailing whitespace and slashes from the path
export const trimPath = partialRight(trim, ' /')
export const resolvePathSegment = flow([resolvePath, trimPath])
/**
* The Route class is a simple and symantic way to represent Routes.
* @class
*/
export class Route {
/**
* Create a Route.
* @param {Route|string} ...pathParts - list of paths and/or routes
*/
constructor(...pathParts) {
invariant(
pathParts.every(isValidPathOrRoute),
'(router/utils...) createRoute(): Expected args to contain only path strings and/or other Routes'
)
this.path = pathParts
}
/**
* @returns {string}
*/
toString() {
return this.path
}
/**
* @returns {string} - path for the route
*/
get path() {
return this[PATH]
}
/**
* Sets the private path variable and adds a preceding slash. Throws an error
* if the path has already been set before. It doesn't make sense to support
* changing the path - Route paths cannot change.
* @param {string} path
*/
set path(args) {
invariant(
!this[PATH],
`(/router/utils...) createRoute(): The Route ${this.path} already has a path defined. You cannot change the path.`
)
const pathSegments = args.map(resolvePathSegment)
this[PATH] = `/${pathSegments.join('/')}`
/**
* Fills in the path with the expected route params.
* @type {Function}
* @param {object} params - the params to build out the path.
* @example
* If the route has a path that looks like this:
* `/authors/:authorId/books/:bookId`
*
* The buildPath call should look like:
* `route.buildPath({ authorId: 1, bookId: 2 })`
* @returns {string} the path including params
*/
this.buildPath = compile(this[PATH])
}
/**
* This function finds all of the route's dynamic params and returns an array
* of their names.
* @returns {array} Array of the route's parameters
*/
get params() {
if (this[PARAMS]) return this[PARAMS]
const keys = []
pathToRegexp(this.path, keys)
this[PARAMS] = map(keys, 'name')
return this[PARAMS]
}
/**
* Matches a pathname against the Route. If the pathname matches the Route,
* a react-router `match` object is returned; otherwise, it returns `null`.
* @see {@link https://reacttraining.com/react-router/web/api/match}
* @param {string} pathname - path to test
* @param {Object} [options] - options passed to `matchPath`
* @param {boolean} [options.exact=false] - when `true`, will only match if
* the route matches the pathname exactly; otherwise, sub routes will match
* @param {boolean} [options.strict=false] - enforces trailing slashes
* @return {Object}
*/
match(pathname, options) {
return matchPath(
pathname,
Object.assign({}, options, {
path: this.path,
})
)
}
/**
* Wraps the `match` method, but instead of returning a `match` object, it
* returns a boolean.
* @see {@link Route#match}
* @param {*} ...args - args passed to `match`
* @return {boolean}
*/
test(...args) {
return Boolean(this.match(...args))
}
}
/**
* Instantiates a new Route.
* @param {Route|string} parentRouteOrPath - either a parent route or the path
* @param {string} [path] - if parent route is provided, this should be path
* @returns Instance of Route
*/
export function createRoute(...args) {
return new Route(...args)
}
import { createRoute } from './utils'
export const users = createRoute('users')
export const usersCreate = createRoute(users, 'create')
export const usersEdit = createRoute(users, 'edit/:userId')
const path = usersEdit.buildPath({userId: '1245'})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment