Last active
June 30, 2020 13:55
-
-
Save jcarroll2007/1ce9d172faf34fc0c1d115865ff2746c to your computer and use it in GitHub Desktop.
A util for creating route paths. It would be great to add types to this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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