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

feat(Ref): implement innerRef on all components #1879

Closed
wants to merge 25 commits into from
Closed

Conversation

layershifter
Copy link
Member

@layershifter layershifter commented Jul 20, 2017

WIP

Fixes #1321.

Why?

ref isn't a prop, it has a same behaviour as key - it never passed to a component. However, there were numerous issues about refs (#1740, #1602, #1849, #1732 and more) and it's time to make something in this way. document.querySelector is a workaround, but it's not a solution.

Problem is a well known in React's world, there is a long-live issue facebook/react#4213, there was also the RFC facebook/react#6974 from Dan.

Variants

Project Prop Example
formsy-react-components componentRef & innerRef refs.md
office-ui-fabric-react componentRef office-ui-fabric-react#1356
react-redux connect HOC, withRef reduxjs/react-redux@2d3d0be
react-router innerRef Link.md

TODO

  • fix broken tests
  • update ref and trigger usages
  • update context and scrollContext usages

@levithomason
Copy link
Member

Very cool, thanks for getting this going. It has been a long time running and will solve many issues. ❤️

I assume this PR will also apply this to all the problem areas noted in the original issue?

@layershifter
Copy link
Member Author

Yep, however there is an unresolvable problem with a HOC.

const RefsSupported = Ref(RefsNotSupported)
<RefsSupported ref={node => console.log(node)} />

There is no way to make this working because ref is not a prop, so component doesn't know anything about it. I've read many issues about this in React's repository and there is no any progress. I think that we should add a componentRef or innerRef prop to each component like React Bootstrap team done. But I need to do more progress before I can make final proposals.

@levithomason
Copy link
Member

Agreed, in my example on react.run I think I used withDOMNode={this.handleDOMNode} or something similar. My thought was that it will not return the ref, but the DOM node. Their site no longer works, so the example is not accessible.

@codecov-io
Copy link

codecov-io commented Jul 24, 2017

Codecov Report

Merging #1879 into master will decrease coverage by 16.91%.
The diff coverage is 100%.

Impacted file tree graph

@@             Coverage Diff             @@
##           master    #1879       +/-   ##
===========================================
- Coverage   99.73%   82.82%   -16.92%     
===========================================
  Files         152      147        -5     
  Lines        2656     2492      -164     
===========================================
- Hits         2649     2064      -585     
- Misses          7      428      +421
Impacted Files Coverage Δ
src/modules/Dropdown/Dropdown.js 75.77% <ø> (-24.23%) ⬇️
src/elements/Button/Button.js 100% <ø> (ø) ⬆️
src/addons/TextArea/TextArea.js 100% <ø> (ø) ⬆️
src/modules/Modal/Modal.js 83.78% <ø> (-16.22%) ⬇️
src/hoc/withRef.js 100% <100%> (ø)
src/elements/Image/Image.js 100% <100%> (ø) ⬆️
src/addons/Ref/Ref.js 100% <100%> (ø) ⬆️
src/behaviors/Visibility/Visibility.js 96.22% <100%> (-3.78%) ⬇️
src/modules/Popup/Popup.js 34.9% <100%> (-65.1%) ⬇️
src/modules/Search/Search.js 16.06% <0%> (-83.41%) ⬇️
... and 151 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6ef5af5...9fcdf3c. Read the comment docs.

@layershifter layershifter force-pushed the feat/ref branch 2 times, most recently from 574158a to f1e6df7 Compare August 1, 2017 16:33
}
}

export default (...args) => dive(new Wrapper(...args))
Copy link
Member Author

Choose a reason for hiding this comment

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

@levithomason I need you there.

After I injected withRef HOC to getElementType I received many broken tests. Half of then fixed with the code above, its idea to dive automatically when shallow is called.

Another half shoud be fixed with overriding of the shallow method on ShallowWrapper, but the inheritance doesn't work. WTF? Can you explain what I'm doing wrong? I think that I'm missing something basic, but at now it destroys my mind. phantomjs doesn't support neither Proxy, nor __noSuchMethod__ 😢

Example test:

 shallow(<Confirm cancelButton='foo' />)
   .find('Button')
   .first()
    .shallow()
    .should.have.text('foo')
expected <Button /> to have text 'foo', but it has '<refHOC />'

Copy link
Member Author

Choose a reason for hiding this comment

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

const shallowMethod = ShallowWrapper.prototype.shallow

ShallowWrapper.prototype.shallow = function (...args) {
  return dive(shallowMethod.apply(this, ...args))
}

I found way to solve this, looks ugly, but works. Open for better solution

Copy link
Member Author

@layershifter layershifter left a comment

Choose a reason for hiding this comment

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

I returned to this PR and want to finish it before this weekend because I want to get initial review on it. I fixed all elements tests, will back to all other tomorrow.

@layershifter
Copy link
Member Author

@levithomason I didn't finished this PR, but I want to get your review. There are many things that don't make me happy.

getElementType

Now it returns an withRef hoc instead of a tag. It's not fatal, but requires the rewrite of code where we relied on tagName like there and there.

withRef

This hoc contains magic conditions. I'm not sure that it's the best idea, but calls of findDOMNode aren't performance-free and we should omit them.

Tests

Most of test are broken because now we get withRef hoc instead of component when we do shallow or mount. I've made shallow wrapper, but it also adds problems:


@levithomason I can fix all left tests, but I need comments and signal from you, that I've chosen the right way 😄


import Ref from '../addons/Ref'

const withRef = Child => class refHOC extends Component {
Copy link
Member

@levithomason levithomason Sep 1, 2017

Choose a reason for hiding this comment

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

Capitalized classes, please.

nit: Could also just be WithRef

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice catch 👍

@@ -19,7 +21,7 @@ function getElementType(Component, props, getDefault) {
// computed default element type

if (getDefault) {
const computedDefault = getDefault()
const computedDefault = typeof getDefault === 'function' ? getDefault() : getDefault
Copy link
Member

@levithomason levithomason Sep 1, 2017

Choose a reason for hiding this comment

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

If this change is made, the signature needs to be updated as well.

 * @param {function} [getDefault] A function that returns a default element type.

That said, I'm not sure there is too much advantage to allowing a conditional here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I used original as you wrote below, so this change was reverted.

@levithomason
Copy link
Member

levithomason commented Sep 1, 2017

Now it returns an withRef hoc instead of a tag. It's not fatal, but requires the rewrite of code where we relied on tagName like there and there.

As for the image example, this is actually a bug :) The 3rd argument is a function:

-const ElementType = getElementType(Image, props, wrappedImage && 'div')
+const ElementType = getElementType(Image, props, () => wrappedImage && 'div')

getElementType.js

* @param {function} [getDefault] A function that returns a default element type.

As for the list item example, I'm not entirely sure right now. One idea, but feels hacky :( is to have withRef add the Child component to the refHOC as a static, like originalComponent. Then, we could check it something like:

<Button as='div' />

const Button (props) => {
  const ElementType = getElementType(Button, props)

  // given the above invocation:
  // ElementType.originalComponent === 'div'

  return <ElementType />
}

I'm not super fond of this, but it seems like one of the only possibilities for "unwrapping" the HOC and accessing the original component.

@levithomason
Copy link
Member

I will have to come back and address the others another time!

@levithomason
Copy link
Member

Note, context props should also use the Ref component. This way, other React elements can be passed as context nodes. See #1978 (comment).

@layershifter
Copy link
Member Author

Requires #2142.

@levithomason
Copy link
Member

Unblocked, has some conflicts.

…-Org/Semantic-UI-React into feat/ref

# Conflicts:
#	src/addons/Ref/Ref.d.ts
#	src/addons/Ref/Ref.js
#	src/collections/Breadcrumb/BreadcrumbSection.js
#	src/index.js
#	test/specs/addons/Ref/Ref-test.js
#	test/specs/addons/Ref/fixtures.js
@layershifter layershifter force-pushed the feat/ref branch 2 times, most recently from 2317aa1 to 11fef64 Compare October 2, 2017 07:17
@layershifter layershifter changed the title feat(Ref): implement innerRef on all our components feat(Ref): implement innerRef on all components Oct 2, 2017
@layershifter
Copy link
Member Author

Needs #2144.

@levithomason
Copy link
Member

#2144 is now unblocked.

@levithomason
Copy link
Member

Note, #2144 was merged and this PR should be clear to move forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ensure DOM nodes can be found for trigger elements and as components
3 participants