Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the withRef mess #3740

Merged
merged 1 commit into from
Aug 19, 2016
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
100 changes: 54 additions & 46 deletions modules/__tests__/withRouter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ import { render, unmountComponentAtNode } from 'react-dom'
import createHistory from '../createMemoryHistory'
import Route from '../Route'
import Router from '../Router'
import routerShape from '../PropTypes'
import withRouter from '../withRouter'

describe('withRouter', function () {
class App extends Component {
propTypes: {
router: routerShape.isRequired
}
testFunction() {
return 'hello from the test function'
}
render() {
expect(this.props.router).toExist()
return <h1>App</h1>
}
const routerStub = {
push() {},
replace() {},
go() {},
goBack() {},
goForward() {},
setRouteLeaveHook() {},
isActive() {}
}

let node
Expand All @@ -30,51 +26,63 @@ describe('withRouter', function () {
unmountComponentAtNode(node)
})

it('puts router on context', function (done) {
const WrappedApp = withRouter(App)
it('should put router on props', function (done) {
const MyComponent = withRouter(({ router }) => {
expect(router).toExist()
done()
return null
})

function App() {
return <MyComponent /> // Ensure no props are passed explicitly.
}

render((
<Router history={createHistory('/')}>
<Route path="/" component={WrappedApp} />
<Route path="/" component={App} />
</Router>
), node, function () {
done()
})
), node)
})

it('still uses router prop if provided', function (done) {
const Test = withRouter(function (props) {
props.test(props)
it('should set displayName', function () {
function MyComponent() {
return null
})
const router = {
push() {},
replace() {},
go() {},
goBack() {},
goForward() {},
setRouteLeaveHook() {},
isActive() {}
}
const test = function (props) {
expect(props.router).toBe(router)
}

render(<Test router={router} test={test} />, node, done)
MyComponent.displayName = 'MyComponent'

expect(withRouter(MyComponent).displayName)
.toEqual('withRouter(MyComponent)')
})

it('should use router prop if specified', function (done) {
const MyComponent = withRouter(({ router }) => {
expect(router).toBe(routerStub)
done()
return null
})

render(<MyComponent router={routerStub} />, node)
})

it('should support withRefs as a parameter', function (done) {
const WrappedApp = withRouter(App, { withRef: true })
const router = {
push() {},
replace() {},
go() {},
goBack() {},
goForward() {},
setRouteLeaveHook() {},
isActive() {}
it('should support withRef', function () {
const spy = expect.createSpy()

class MyComponent extends Component {
invokeSpy() {
spy()
}

render() {
return null
}
}
const component = render((<WrappedApp router={router}/>), node, done)
expect(component.getWrappedInstance().testFunction()).toEqual('hello from the test function')

const WrappedComponent = withRouter(MyComponent, { withRef: true })

const instance = render(<WrappedComponent router={routerStub} />, node)
instance.getWrappedInstance().invokeSpy()

expect(spy).toHaveBeenCalled()
})
})
23 changes: 15 additions & 8 deletions modules/withRouter.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import invariant from 'invariant'
import React from 'react'
import hoistStatics from 'hoist-non-react-statics'
import { routerShape } from './PropTypes'
import warning from './routerWarning'


function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

export default function withRouter(WrappedComponent, options) {
const { withRef } = options || {}
const withRef = options && options.withRef

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather do destructuring in case the options grow. If they won't ever (I dunno, maybe?) then this should just be a simple boolean arg.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figure deal with that when we get there, and for now skip creating an empty object that doesn't get any use.

const WithRouter = React.createClass({
contextTypes: { router: routerShape },
propTypes: { router: routerShape },

getWrappedInstance() {
warning(withRef, 'To access the wrappedInstance you must provide { withRef: true } as the second argument of the withRouter call')
return this.wrappedComponent
invariant(
withRef,
'To access the wrapped instance, you need to specify ' +
'`{ withRef: true }` as the second argument of the withRouter() call.'
)

return this.wrappedInstance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is the right name. Forgot to switch that out!

},

render() {
const { router, ...props } = this.props
const router = this.props.router || this.context.router
const props = { ...this.props, router }

if (withRef) props.ref = component =>this.wrappedComponent = component
if (withRef) {
props.ref = (c) => { this.wrappedInstance = c }
}

return <WrappedComponent {...props} router={router || this.context.router} />
return <WrappedComponent {...props} />
}
})

Expand Down