-
Notifications
You must be signed in to change notification settings - Fork 53
feat(Ref): support of forwardRef()
API
#491
Changes from 5 commits
62929d9
9882478
46c6913
17cbc97
33c95c1
57faf87
bb93b77
306ca3b
97594a7
c418eec
747931c
d11dcc4
1501b0d
0d75b11
6f0039f
1c77d7b
31de50a
5642eab
bce1d43
098ee71
a2f7fd1
8e41c02
b8a3384
e1d73e1
921f35a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import React from 'react' | ||
import { Grid, Ref, Segment } from '@stardust-ui/react' | ||
|
||
const ExampleButton = React.forwardRef<HTMLButtonElement>((props, ref) => ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The usage of generic in docs :( It's impossible to omit it there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, and this is definitely not the problem of this PR :) seems that we should use JS engine to process this code, as docs are aimed to present JS variant of examples to the client (this is the case for now, at least) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm planning to left this thing as is and push #495 that will resolve all our issues. |
||
<div> | ||
<button {...props} ref={ref} /> | ||
</div> | ||
)) | ||
|
||
class RefExampleForwardRef extends React.Component { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually, very confused by trying to understand how Let me explain resaons for this by following the example. Suppose that I am a Stardust client and have the following component: const MyButton = React.forwardRef((props, ref) => (
<div>
<button {...props} ref={ref} />
</div>
)) With this component I already expect the following to be true: // when mounted, this.ref.current will refer to <button />
<MyButton ref={this.ref} /> Then comes Stardust and proposes to use
const WithRef = (props) => (
<Ref innerRef={props.forwardedRef}>
{ React.forwardRef((props, ref) => <MyButton ref={ref} />
</Ref>
)
|
||
forwardedRef = React.createRef<HTMLButtonElement>() | ||
state = { isMounted: false } | ||
|
||
componentDidMount() { | ||
this.setState({ isMounted: true }) | ||
} | ||
|
||
render() { | ||
const { isMounted } = this.state | ||
const buttonNode = this.forwardedRef.current | ||
|
||
return ( | ||
<Grid columns={2}> | ||
<Segment> | ||
<p> | ||
A button below uses <code>forwardRef</code> API. | ||
</p> | ||
|
||
<Ref innerRef={this.forwardedRef}> | ||
<ExampleButton>A button</ExampleButton> | ||
</Ref> | ||
</Segment> | ||
|
||
{isMounted && ( | ||
<code style={{ margin: 10 }}> | ||
<pre> | ||
{JSON.stringify( | ||
{ | ||
nodeName: buttonNode.nodeName, | ||
nodeType: buttonNode.nodeType, | ||
textContent: buttonNode.textContent, | ||
}, | ||
null, | ||
2, | ||
)} | ||
</pre> | ||
</code> | ||
)} | ||
</Grid> | ||
) | ||
} | ||
} | ||
|
||
export default RefExampleForwardRef |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,7 @@ | |
"lodash": "^4.17.10", | ||
"prop-types": "^15.6.1", | ||
"react-fela": "^7.2.0", | ||
"react-is": "^16.6.3", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (!) A new dependency. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
"react-popper": "^1.0.2", | ||
"what-input": "^5.1.2" | ||
}, | ||
|
@@ -105,6 +106,7 @@ | |
"@types/react": "^16.3.17", | ||
"@types/react-custom-scrollbars": "^4.0.5", | ||
"@types/react-dom": "^16.0.6", | ||
"@types/react-is": "^16.5.0", | ||
"@types/react-router": "^4.0.27", | ||
"awesome-typescript-loader": "^5.2.1", | ||
"connect-history-api-fallback": "^1.3.0", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,37 @@ | ||
import * as PropTypes from 'prop-types' | ||
import * as React from 'react' | ||
import { findDOMNode } from 'react-dom' | ||
import { isForwardRef } from 'react-is' | ||
|
||
import { ReactChildren } from '../../../types/utils' | ||
import { handleRef } from '../../lib' | ||
|
||
export interface RefProps { | ||
children?: ReactChildren | ||
children: ReactChildren | ||
innerRef?: React.Ref<any> | ||
} | ||
|
||
export interface RefState { | ||
child: React.ReactElement<any> & { ref: React.Ref<any> } | ||
isForward: boolean | ||
} | ||
|
||
/** | ||
* This component exposes a callback prop that always returns the DOM node of both functional and class component | ||
* children. | ||
*/ | ||
export default class Ref extends React.Component<RefProps> { | ||
export default class Ref extends React.Component<RefProps, RefState> { | ||
state = { | ||
child: null, | ||
isForward: false, | ||
} | ||
|
||
static propTypes = { | ||
/** | ||
* Used to set content when using childrenApi - internal only | ||
* @docSiteIgnore | ||
*/ | ||
children: PropTypes.element, | ||
children: PropTypes.element.isRequired, | ||
|
||
/** | ||
* Called when a child component will be mounted or updated. | ||
|
@@ -30,15 +41,39 @@ export default class Ref extends React.Component<RefProps> { | |
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), | ||
} | ||
|
||
static getDerivedStateFromProps(props: RefProps) { | ||
const child = React.Children.only(props.children) | ||
|
||
return { | ||
child, | ||
isForward: isForwardRef(child), | ||
} | ||
} | ||
|
||
layershifter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
componentDidMount() { | ||
handleRef(this.props.innerRef, findDOMNode(this)) | ||
if (!this.state.isForward) { | ||
layershifter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
handleRef(this.props.innerRef, findDOMNode(this)) | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
handleRef(this.props.innerRef, null) | ||
if (!this.state.isForward) { | ||
handleRef(this.props.innerRef, null) | ||
} | ||
} | ||
|
||
private handleRefOverride = (node: HTMLElement) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. according to ref's docs: https://reactjs.org/docs/refs-and-the-dom.html
The parameter type used in this function leads to false assumptions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See my comment below, we will enforce user to pass correct refs. |
||
handleRef(this.state.child.ref, node) | ||
handleRef(this.props.innerRef, node) | ||
} | ||
|
||
render() { | ||
return this.props.children && React.Children.only(this.props.children) | ||
const { child, isForward } = this.state | ||
|
||
return isForward | ||
? React.cloneElement(child, { | ||
ref: this.handleRefOverride, | ||
}) | ||
: child | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.