Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: node_js
node_js:
- "8.9.0"
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.5.1
- export PATH="$HOME/.yarn/bin:$PATH"
script:
- yarn run travis
52 changes: 52 additions & 0 deletions MIGRATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Migrating from 1.x.x -> 2.x.x

> 1.) `withBreadcrumbs` is now the default export

_1.x.x_
```js
import { withBreadcrumbs } from 'react-router-breadcrumbs-hoc';
```

_2.x.x_
```js
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
```

> 2.) The breadcrumbs array returned by the HOC is now _just_ the components. It _used_ to be an array of objects, but I decided this approach was easier to understand and made the implementation code a bit cleaner.

_1.x.x_
```js
{breadcrumbs.map(({ breadcrumb, path, match }) => (
<span key={path}>
<NavLink to={match.url}>
{breadcrumb}
</NavLink>
</span>
))}
```

_2.x.x_
```js
{breadcrumbs.map(breadcrumb => (
<span key={breadcrumb.props.key}>
<NavLink to={breadcrumb.props.match.url}>
{breadcrumb}
</NavLink>
</span>
))}
```

> 3.) The package will now attempt to return sensible defaults for breadcrumbs unless otherwise provided making the the package now "opt-out" instead of "opt-in" for all paths. See the readme for how to disable default breadcrumb behavior.

_1.x.x_
```js
withBreadcrumbs([
{ path: '/', breadcrumb: 'Home' },
{ path: '/users', breadcrumb: 'Users' },
])(Component);
```

_2.x.x_ (the above breadcrumbs will be automagically generated so there's no need to include them in config)
```js
withBreadcrumbs()(Component);
```
98 changes: 53 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
</h3>

<p align="center">
Just a tiny, flexible, <a href="https://reactjs.org/docs/higher-order-components.html">higher order component</a> for rendering breadcrumbs with <a href="https://github.com/ReactTraining/react-router">react-router</a> 4.x
A tiny (~2kb minified), flexible, <a href="https://reactjs.org/docs/higher-order-components.html">higher order component</a> for rendering breadcrumbs with <a href="https://github.com/ReactTraining/react-router">react-router</a> 4.x
</p>

<p align="center">
site.com/user/id → user / John Doe
site.com/user/id → Home / User / John Doe
</p>

<p align="center">
Expand All @@ -34,35 +34,36 @@ or
## Usage

```js
withBreadcrumbs(routeConfigObject)(MyComponent);
withBreadcrumbs()(MyComponent);
```

## Example
## Simple example

```js
import React from 'react';
import { NavLink } from 'react-router-dom';
import { withBreadcrumbs } from 'react-router-breadcrumbs-hoc';
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';

// breadcrumbs can be any type of component or string
const UserBreadcrumb = ({ match }) =>
<span>{match.params.userId}</span>; // use match param userId to fetch/display user name

// define some custom breadcrumbs for certain routes (optional)
const routes = [
{ path: '/', breadcrumb: 'Home' },
{ path: '/users', breadcrumb: 'Users' },
{ path: '/users/:userId', breadcrumb: UserBreadcrumb },
{ path: '/something-else', breadcrumb: ':)' },
{ path: '/example', breadcrumb: 'Custom Example' },
];

// map & render your breadcrumb components however you want
// map & render your breadcrumb components however you want.
// each `breadcrumb` has the props `key`, `location`, and `match` included!
const Breadcrumbs = ({ breadcrumbs }) => (
<div>
{breadcrumbs.map(({ breadcrumb, path, match }) => (
<span key={path}>
<NavLink to={match.url}>
{breadcrumbs.map((breadcrumb, index) => (
<span key={breadcrumb.props.key}>
<NavLink to={breadcrumb.props.match.url}>
{breadcrumb}
</NavLink>
<span>/</span>
{(index < breadcrumbs.length - 1) && <i> / </i>}
</span>
))}
</div>
Expand All @@ -77,72 +78,79 @@ Pathname | Result
--- | ---
/users | Home / Users
/users/id | Home / Users / John
/something-else | Home / :)
/example | Home / Custom Example

## Disabling default breadcrumbs for paths

This package will attempt to create breadcrumbs for you based on the route section via [humanize-string](https://github.com/sindresorhus/humanize-string). For example `/users` will auotmatically create the breadcrumb `"Users"`. There are two ways to disable default breadcrumbs for a path:

1.) Pass `breadcrumb: null` in the routes config
`{ path: '/a/b', breadcrumb: null }`

2.) Pass an `excludePaths` array in the `options`
`withBreadcrumbs(routes, { excludePaths: ['/', '/no-breadcrumb/for-this-route'] })`

in your routes array.

## Already using a [route config](https://reacttraining.com/react-router/web/example/route-config) array with react-router?

Just add a `breadcrumbs` prop to your routes that require breadcrumbs!
Just add a `breadcrumbs` prop to your routes that require custom breadcrumbs.

> Note: currently nested `routes` arrays are not supported, but will be soon (see: https://github.com/icd2k3/react-router-breadcrumbs-hoc/issues/24)
> Note: currently, nested `route`s arrays are _not_ supported, but will be soon (see: https://github.com/icd2k3/react-router-breadcrumbs-hoc/issues/24)

## API

```js
Route = {
path: String
breadcrumb: String|Function
breadcrumb: String|Function? // note: if not provided, a default breadcrumb will be returned
matchOptions?: Object
}

Breadcrumb = {
path: String
match: String
breadcrumb: Component
Options = {
excludePaths: Array
}

// react-router's location object: https://reacttraining.com/react-router/web/api/location
Location = {
key: String
pathname: String
search: String
hash: String
state: Object
}

withBreadcrumbs(routes: Array<Route>): HigherOrderComponent
// if routes are not passed, default breadcrumbs will be returned
withBreadcrumbs(routes?: Array<Route>, options? Object<Options>): HigherOrderComponent

// you shouldn't ever really have to use `getBreadcrumbs`, but it's
// exported for convenience if you don't want to use the HOC
getBreadcrumbs({ routes: Array<Route>, location: Location }): Array<Breadcrumb>
getBreadcrumbs({
routes: Array<Route>,
location: Object<Location>, // react-router's location object: https://reacttraining.com/react-router/web/api/location
options: Object<Options>,
}): Array<Breadcrumb>
```

## Order Matters!
## Order matters!

Consider the following route config:
Consider the following route configs:

```js
[
{ path: '/users', breadcrumb: 'Users' },
{ path: '/users/:id', breadcrumb: 'Users - id' },
{ path: '/users/create', breadcrumb: 'Users - create' },
{ path: '/users/:id', breadcrumb: 'id-breadcrumb' },
{ path: '/users/create', breadcrumb: 'create-breadcrumb' },
]
```

This package acts like a switch statement and matches the first breadcrumb it can find. So, unfortunately, visiting `/users/create` will result in the `Users > Users - id` breadcrumbs instead of the desired `Users > Users - create` breadcrumbs.
// example.com/users/create = 'id-breadcrumb' (because path: '/users/:id' will match first)
// example.com/users/123 = 'id-breadcumb'
```

To get the right breadcrumbs, simply change the order:
To fix the issue above, just adjust the order of your routes:

```js
[
{ path: '/users', breadcrumb: 'Users' },
{ path: '/users/create', breadcrumb: 'Users - create' },
{ path: '/users/:id', breadcrumb: 'Users - id' },
{ path: '/users/create', breadcrumb: 'create-breadcrumb' },
{ path: '/users/:id', breadcrumb: 'id-breadcrumb' },
]

// example.com/users/create = 'create-breadcrumb' (because path: '/users/create' will match first)
// example.com/users/123 = 'id-breadcrumb'
```

Now, `/users/create` will match the create breadcrumb first, and all others will fall through to `/:id`.
## Using the location object

## Using the Location Object
React Router's [location](https://reacttraining.com/react-router/web/api/location) object lets you pass `state` property. Using the `state` allows one to update the Breadcrumb to display dynamic info at runtime. Consider this example:

```jsx
Expand Down
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@ module.exports = {
},
},
setupFiles: ['./jest.setup.js'],
snapshotSerializers: ['enzyme-to-json/serializer'],
};
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-router-breadcrumbs-hoc",
"version": "1.2.0",
"version": "2.0.0",
"description": "Just a tiny, flexible, higher order component for rendering breadcrumbs with react-router 4.x",
"repository": "icd2k3/react-router-breadcrumbs-hoc",
"keywords": [
Expand All @@ -19,6 +19,9 @@
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2"
},
"dependencies": {
"humanize-string": "^1.0.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.8.0",
Expand All @@ -30,11 +33,9 @@
"babel-preset-react": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"coveralls": "^3.0.0",
"cross-env": "^5.1.3",
"enzyme": "^3.2.0",
"enzyme-adapter-react-16": "^1.1.0",
"enzyme-to-json": "^3.3.0",
"eslint": "^4.18.1",
"eslint": "^4.18.2",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.9.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
Expand All @@ -45,16 +46,16 @@
"react-dom": "^16.2.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"rollup": "^0.56.3",
"rollup": "^0.56.5",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-node-resolve": "^3.0.2"
"rollup-plugin-commonjs": "^9.0.0",
"rollup-plugin-node-resolve": "^3.2.0"
},
"scripts": {
"prepublishOnly": "npm run build",
"build": "rollup -c",
"test": "cross-env NODE_ENV=test jest",
"travis": "jest && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"test": "jest",
"travis": "yarn run lint && jest && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"lint": "eslint ./src/**"
}
}
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const globals = {

const config = {
input: 'src/index.js',
exports: 'named',
plugins: [
babel({
exclude: 'node_modules/**',
Expand Down
Loading