Skip to content

Commit

Permalink
Add <Link replace> and <Redirect push> (#3912)
Browse files Browse the repository at this point in the history
fixes #3903
  • Loading branch information
aaugustin authored and ryanflorence committed Oct 6, 2016
1 parent e5be03a commit eac8217
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 19 deletions.
9 changes: 7 additions & 2 deletions modules/Link.js
Expand Up @@ -6,6 +6,7 @@ import {

class Link extends React.Component {
static defaultProps = {
replace: false,
activeOnlyWhenExact: false,
className: '',
activeClassName: '',
Expand Down Expand Up @@ -38,12 +39,15 @@ class Link extends React.Component {
isLeftClickEvent(event)
) {
event.preventDefault()
this.context.router.transitionTo(this.props.to)
this.handleTransition()
}
}

handleTransition = () => {
this.context.router.transitionTo(this.props.to)
const { router } = this.context
const { to, replace } = this.props
const navigate = replace ? router.replaceWith : router.transitionTo
navigate(to)
}

render() {
Expand Down Expand Up @@ -107,6 +111,7 @@ class Link extends React.Component {
if (__DEV__) {
Link.propTypes = {
to: PropTypes.oneOfType([ PropTypes.string, PropTypes.object ]).isRequired,
replace: PropTypes.bool,
activeStyle: PropTypes.object,
activeClassName: PropTypes.string,
location: PropTypes.object,
Expand Down
14 changes: 11 additions & 3 deletions modules/Redirect.js
Expand Up @@ -4,6 +4,10 @@ import {
} from './PropTypes'

class Redirect extends React.Component {
static defaultProps = {
push: false
}

static contextTypes = {
router: routerContextType,
serverRouter: PropTypes.object
Expand All @@ -20,9 +24,12 @@ class Redirect extends React.Component {

redirect() {
const { router } = this.context
const { to, push } = this.props
// so that folks can unit test w/o hassle
if (router)
router.replaceWith(this.props.to)
if (router) {
const navigate = push ? router.transitionTo : router.replaceWith
navigate(to)
}
}

render() {
Expand All @@ -35,7 +42,8 @@ if (__DEV__) {
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]).isRequired
]).isRequired,
push: PropTypes.bool
}
}

Expand Down
103 changes: 92 additions & 11 deletions modules/__tests__/integration-test.js
Expand Up @@ -2,13 +2,23 @@ import expect from 'expect'
import React from 'react'
import Router from '../MemoryRouter'
import NavigationPrompt from '../NavigationPrompt'
import Redirect from '../Redirect'
import StaticRouter from '../StaticRouter'
import Match from '../Match'
import Miss from '../Miss'
import { Simulate } from 'react-addons-test-utils'
import Link from '../Link'
import { render } from 'react-dom'

const requiredPropsForStaticRouter = {
location: '/',
action: 'POP',
createHref: () => {},
blockTransitions: () => {}, // we sure we want this required? servers don't need it.
onPush: () => {},
onReplace: () => {}
}

describe('Integration Tests', () => {

it('renders root match', () => {
Expand Down Expand Up @@ -254,6 +264,46 @@ describe('clicking around', () => {
Simulate.click(div.querySelector('#one'), leftClickEvent)
expect(div.innerHTML).toContain(TEXT1)
})

it('pushes a new URL', () => {
const div = document.createElement('div')
const pushes = []
const replaces = []
const TARGET = '/TARGET'
render((
<StaticRouter
{...requiredPropsForStaticRouter}
onPush={(loc) => { pushes.push(loc) }}
onReplace={(loc) => { replaces.push(loc) }}
>
<Link id="target" to={TARGET}>{TARGET}</Link>
</StaticRouter>
), div)
Simulate.click(div.querySelector('#target'), leftClickEvent)
expect(pushes.length).toEqual(1)
expect(pushes[0].pathname).toEqual(TARGET)
expect(replaces.length).toEqual(0)
})

it('replaces the current URL with replace', () => {
const div = document.createElement('div')
const pushes = []
const replaces = []
const TARGET = '/TARGET'
render((
<StaticRouter
{...requiredPropsForStaticRouter}
onPush={(loc) => { pushes.push(loc) }}
onReplace={(loc) => { replaces.push(loc) }}
>
<Link id="target" to={TARGET} replace>{TARGET}</Link>
</StaticRouter>
), div)
Simulate.click(div.querySelector('#target'), leftClickEvent)
expect(pushes.length).toEqual(0)
expect(replaces.length).toEqual(1)
expect(replaces[0].pathname).toEqual(TARGET)
})
})

describe('Link location descriptors', () => {
Expand Down Expand Up @@ -322,23 +372,14 @@ describe('Link with a query', () => {

describe('Match and Miss Integration', () => {

const requiredProps = {
location: '/',
action: 'POP',
createHref: () => {},
blockTransitions: () => {}, // we sure we want this required? servers don't need it.
onPush: () => {},
onReplace: () => {}
}

describe('Miss', () => {
it('renders when nothing else matches', () => {
const div = document.createElement('div')
const FOO = '/FOO'
const MISS = '/MISS'
render((
<StaticRouter
{...requiredProps}
{...requiredPropsForStaticRouter}
location={{ pathname: MISS }}
>
<div>
Expand All @@ -357,7 +398,7 @@ describe('Match and Miss Integration', () => {
const MISS = '/MISS'
render((
<StaticRouter
{...requiredProps}
{...requiredPropsForStaticRouter}
location={{ pathname: FOO }}
>
<div>
Expand Down Expand Up @@ -402,3 +443,43 @@ describe('NavigationPrompt', () => {
expect(message).toEqual(TEXT)
})
})

describe('Redirect', () => {
it('replaces the current URL', () => {
const div = document.createElement('div')
const pushes = []
const replaces = []
const REDIRECTED = '/REDIRECTED'
render((
<StaticRouter
{...requiredPropsForStaticRouter}
onPush={(loc) => { pushes.push(loc) }}
onReplace={(loc) => { replaces.push(loc) }}
>
<Redirect to={REDIRECTED} />
</StaticRouter>
), div)
expect(pushes.length).toEqual(0)
expect(replaces.length).toEqual(1)
expect(replaces[0].pathname).toEqual(REDIRECTED)
})

it('pushes a new URL with push', () => {
const div = document.createElement('div')
const pushes = []
const replaces = []
const REDIRECTED = '/REDIRECTED'
render((
<StaticRouter
{...requiredPropsForStaticRouter}
onPush={(loc) => { pushes.push(loc) }}
onReplace={(loc) => { replaces.push(loc) }}
>
<Redirect to={REDIRECTED} push />
</StaticRouter>
), div)
expect(pushes.length).toEqual(1)
expect(pushes[0].pathname).toEqual(REDIRECTED)
expect(replaces.length).toEqual(0)
})
})
11 changes: 10 additions & 1 deletion website/api/Link.md
Expand Up @@ -24,7 +24,7 @@ Children function parameter is an object with the following keys:

```js
<Link to="/courses">{
({isActive, location, href, onClick, transition}) =>
({isActive, location, href, onClick, transition}) =>
<RaisedButton label="Courses" onClick={onClick} primary={isActive} href={href} />
}</Link>
```
Expand Down Expand Up @@ -120,4 +120,13 @@ in the hierarchy.
<Match pattern="/foo" location={this.props.location}/>
```

## `replace: bool`

When true, clicking the link will replace the current history state with
`replaceState` instead of adding a new history state with `pushState`.

```js
<Link to="/courses" replace/>
```

# `</Link>`
15 changes: 13 additions & 2 deletions website/api/Redirect.md
@@ -1,7 +1,9 @@
# `<Redirect>`

Rendering a `Redirect` will navigate to a new location and add the
previous location onto the next location state.
Rendering a `Redirect` will navigate to a new location.

The new location will override the current location in the browser's history,
like server-side redirects (HTTP 3xx) do.

(If this freaks you out you can use the imperative API from the `router`
on context.)
Expand Down Expand Up @@ -37,4 +39,13 @@ A location descriptor to redirect to.
}}/>
```

## `push: bool`

When true, redirecting will add a new history state with `pushState` instead
of replacing the current history state with `replaceState`.

```js
<Redirect to="/somewhere/else" push/>
```

# `</Redirect>`

0 comments on commit eac8217

Please sign in to comment.