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(forwardRef): add forwardRefFactory #2844

Closed
wants to merge 5 commits into from
Closed
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
6 changes: 3 additions & 3 deletions docs/src/components/CarbonAd/CarbonAd.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const waitForLoad = () => {

class CarbonAd extends Component {
componentDidMount() {
this.ifRef((ref) => {
this.ifRef(ref => {
// always add the script as it is used to insert the ad
ref.appendChild(script)

Expand All @@ -55,13 +55,13 @@ class CarbonAd extends Component {
return false
}

ifRef = (cb) => {
ifRef = cb => {
const ref = document.querySelector('#docs-carbonads')
if (ref) cb(ref)
}

render() {
return <div id='docs-carbonads' style={style} />
return <div id="docs-carbonads" style={style} />
}
}

Expand Down
30 changes: 15 additions & 15 deletions docs/src/components/CarbonAd/CarbonAdNative.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class CarbonAdNative extends Component {
document.getElementsByTagName('head')[0].appendChild(this.script)
}

handleNativeJSON = (json) => {
handleNativeJSON = json => {
debug('handleNativeJSON', { mounted: this.mounted })
try {
const sanitizedAd = json.ads
Expand Down Expand Up @@ -91,28 +91,28 @@ class CarbonAdNative extends Component {

const colors = inverted
? {
divider: '#333',
background: '#222',
backgroundHover: '#1d1d1d',
color: '#999',
colorHover: '#ccc',
}
divider: '#333',
background: '#222',
backgroundHover: '#1d1d1d',
color: '#999',
colorHover: '#ccc',
}
: {
divider: '#eee',
background: '#fff',
backgroundHover: 'whitesmoke',
color: '#555',
colorHover: '#333',
}
divider: '#eee',
background: '#fff',
backgroundHover: 'whitesmoke',
color: '#555',
colorHover: '#333',
}

return (
<a id={id} href={ad.statlink} target='_blank' rel='noopener noreferrer'>
<a id={id} href={ad.statlink} target="_blank" rel="noopener noreferrer">
<img src={ad.image} />
<span>{ad.company}</span>
{' — '}
<span>{ad.description}</span>
<Label
content='Ad'
content="Ad"
basic={!inverted}
color={inverted ? 'black' : undefined}
horizontal
Expand Down
6 changes: 3 additions & 3 deletions docs/src/components/CodeEditor/CodeEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const semanticUIReactCompleter = {
}

const addPropsFromInfo = (info, score) => {
info.props.forEach((prop) => {
info.props.forEach(prop => {
completions.push({
score,
caption: `${prop.name}`,
Expand Down Expand Up @@ -76,13 +76,13 @@ const semanticUIReactCompleter = {
const suirNamedImports = value.match(/import\s+\{([\s\S]+?)\}\s+from\s'semantic-ui-react'/)
const importedDisplayNames = _.words(suirNamedImports[1])

importedDisplayNames.forEach((displayName) => {
importedDisplayNames.forEach(displayName => {
addPropsFromDisplayName(displayName, 200)
addComponentDisplayName(displayName, 100)
})

// local words
_.uniq(_.words(value)).forEach((word) => {
_.uniq(_.words(value)).forEach(word => {
completions.push({
score: 0,
caption: word,
Expand Down
10 changes: 5 additions & 5 deletions docs/src/components/CodeEditor/CodeEditorUniveral.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const EDITOR_GUTTER_COLOR = '#25282d'
// component also allows us to load Editor lazy.
const CodeEditor = isBrowser()
? universal(import('./CodeEditor'), {
loading: () => <Loader active inline='centered' />,
})
loading: () => <Loader active inline="centered" />,
})
: () => null

function CodeEditorUniveral(props) {
Expand All @@ -25,9 +25,9 @@ function CodeEditorUniveral(props) {
<CodeEditor
name={id}
mode={mode}
theme='tomorrow_night'
width='100%'
height='100px'
theme="tomorrow_night"
width="100%"
height="100px"
value={value}
enableBasicAutocompletion={!readOnly}
enableLiveAutocompletion={!readOnly}
Expand Down
12 changes: 9 additions & 3 deletions docs/src/examples/elements/Flag/Types/FlagExampleFlag.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React from 'react'
import { Flag, Segment } from 'semantic-ui-react'

const ForwardRef = React.forwardRef((props, ref) => (
<span>
<i {...props} ref={ref} />
</span>
))

const FlagExampleFlag = () => (
<Segment>
<Flag name='ae' />
<Flag name='france' />
<Flag name='myanmar' />
<Flag as={ForwardRef} name='france' ref={c => console.log('ForwardRef', c)} />
<Flag name='myanmar' ref={c => console.log('None', c)} />
<Flag as='div' name='russia' ref={c => console.log('Simple', c)} />
</Segment>
)

Expand Down
10 changes: 5 additions & 5 deletions docs/src/examples/elements/Flag/Types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const FlagTypesExamples = () => (
description='A flag can use the two digit country code, the full name, or a common alias.'
examplePath='elements/Flag/Types/FlagExampleFlag'
/>
<ComponentExample
title='Country names, codes and aliases.'
description=''
examplePath='elements/Flag/Types/FlagExampleTable'
/>
{/*<ComponentExample*/}
{/*title='Country names, codes and aliases.'*/}
{/*description=''*/}
{/*examplePath='elements/Flag/Types/FlagExampleTable'*/}
{/*/>*/}
</ExampleSection>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { Icon } from 'semantic-ui-react'

const IconExampleFitted = () => (
<div>
This icon<Icon fitted name='help' />is fitted.
This icon
<Icon fitted name='help' />
is fitted.
<br />
This icon<Icon name='help' />is not.
This icon
<Icon name='help' />
is not.
</div>
)

Expand Down
11 changes: 4 additions & 7 deletions docs/src/layouts/GridLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ const GridLayout = () => (
<Header as='h3'>Special Variations</Header>
<p>
Some special variations that format grids like tables require you to specify rows. For
example a
<code>divided grid</code> or a <code>celled grid</code> requires row wrappers.
example a<code>divided grid</code> or a <code>celled grid</code> requires row wrappers.
</p>

<Divider section horizontal>
Expand Down Expand Up @@ -242,9 +241,8 @@ const GridLayout = () => (
<Header as='h3'>Centering Content</Header>
<p>
If a row does not take up all sixteen grid columns, you can use a{' '}
<code>ui centered grid</code>,
<code>centered row</code>, or <code>centered column</code> to center the column contents
inside the grid.
<code>ui centered grid</code>,<code>centered row</code>, or <code>centered column</code> to
center the column contents inside the grid.
</p>

<Grid centered columns={2}>
Expand All @@ -258,8 +256,7 @@ const GridLayout = () => (
<Header as='h3'>Floating Rows</Header>
<p>
Since Semantic UI's grid is based on flex box, a <code>left floated</code> item should come
first, and a
<code>right floated</code> item last in its row.
first, and a<code>right floated</code> item last in its row.
</p>

<Grid>
Expand Down
16 changes: 12 additions & 4 deletions docs/src/layouts/StickyLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ export default class StickyLayout extends Component {
</Visibility>

<Container text>
{_.times(3, i => <Paragraph key={i} />)}
{_.times(3, i => (
<Paragraph key={i} />
))}

{/* Example with overlay menu is more complex, SUI simply clones all elements inside, but we should use a
different approach.
Expand Down Expand Up @@ -213,19 +215,25 @@ export default class StickyLayout extends Component {
</Menu>
</div>

{_.times(3, i => <Paragraph key={i} />)}
{_.times(3, i => (
<Paragraph key={i} />
))}
<LeftImage />

<Paragraph />
<RightImage />

{_.times(4, i => <Paragraph key={i} />)}
{_.times(4, i => (
<Paragraph key={i} />
))}
<LeftImage />

<Paragraph />
<RightImage />

{_.times(2, i => <Paragraph key={i} />)}
{_.times(2, i => (
<Paragraph key={i} />
))}
</Container>

<Segment inverted style={{ margin: '5em 0em 0em', padding: '5em 0em' }} vertical>
Expand Down
3 changes: 1 addition & 2 deletions gulp/plugins/gulp-example-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export default () => {
}

try {
const sourceName = _
.split(file.path, path.sep)
const sourceName = _.split(file.path, path.sep)
.slice(-4)
.join('/')
.slice(0, -3)
Expand Down
5 changes: 4 additions & 1 deletion src/elements/Flag/Flag.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, { PureComponent } from 'react'
import {
createShorthandFactory,
customPropTypes,
forwardRefFactory,
getElementType,
getUnhandledProps,
} from '../../lib'
Expand Down Expand Up @@ -529,10 +530,12 @@ class Flag extends PureComponent {
const rest = getUnhandledProps(Flag, this.props)
const ElementType = getElementType(Flag, this.props)

console.log('Flag props', rest)

return <ElementType {...rest} className={classes} />
}
}

Flag.create = createShorthandFactory(Flag, value => ({ name: value }))

export default Flag
export default forwardRefFactory(Flag)
4 changes: 4 additions & 0 deletions src/lib/componentUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const isForwardRef = Component =>
(Component && Component.$$typeof && typeof Component.render === 'function') || false

export const isBasic = Component => typeof Component === 'string'
44 changes: 44 additions & 0 deletions src/lib/forwardRefFactory/forwardFunctionFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { createElement } from 'react'

import Ref from '../../addons/Ref'
import { isBasic, isForwardRef } from '../componentUtils'

/**
* Use just a string for now (react 16.3), since react doesn't support Symbols in props yet.
* https://github.com/facebook/react/issues/7552
* @type {String}
*/
export const forwardRefSymbol = '__forwardRef__'

/**
* Creates a function that will choose how to pass a ref.
*
* @param {Function|Component} Component A Component to wrap
* @return {Function}
*/
const forwardFunctionFactory = Component => (props, ref) => {
console.log('forwardFunctionFactory()', {
element: props.as,
isBasic: isBasic(props.as),
isForwardRef: isForwardRef(props.as),
})

// The most simple case when `as='div'`
// This component supports ref forwarding!
// Magic happens there!
if (!props.as || isBasic(props.as) || isForwardRef(props.as)) {
return createElement(Component, {
...props,
[forwardRefSymbol]: ref,
})
}

// Need to get ref manually
return (
<Ref innerRef={ref}>
<Component {...props} />
</Ref>
)
}

export default forwardFunctionFactory
21 changes: 21 additions & 0 deletions src/lib/forwardRefFactory/forwardRefFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import hoistStatics from 'hoist-non-react-statics'
import { forwardRef } from 'react'

import forwardFunctionFactory from './forwardFunctionFactory'

/**
* Wraps passed component with react 'forwardRef' function, which produce new component with type 'object' and structure
* like so: { $$type: Symbol(), render: function }. Assigns (hoists) static methods of passed component to result
* forward component using 'hoist-non-react-statics' module.
*
* @param {Function|Component} Component A Component to wrap with forwardRef()
* @return {Object}
*/
const forwardRefFactory = (Component) => {
const forwarder = forwardRef(forwardFunctionFactory(Component))

hoistStatics(forwarder, Component, { $$typeof: true, render: true })
return forwarder
}

export default forwardRefFactory
2 changes: 2 additions & 0 deletions src/lib/forwardRefFactory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { forwardRefSymbol } from './forwardFunctionFactory'
export forwardRefFactory from './forwardRefFactory'
6 changes: 6 additions & 0 deletions src/lib/getUnhandledProps.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { forwardRefSymbol } from './forwardRefFactory'

/**
* Returns an object consisting of props beyond the scope of the Component.
* Useful for getting and spreading unknown props from the user.
Expand All @@ -11,6 +13,10 @@ const getUnhandledProps = (Component, props) => {

return Object.keys(props).reduce((acc, prop) => {
if (prop === 'childKey') return acc
if (prop === forwardRefSymbol) {
acc.ref = props[forwardRefSymbol]
return acc
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This hack allows to don't map ref in the every component

if (handledProps.indexOf(prop) === -1) acc[prop] = props[prop]
return acc
}, {})
Expand Down
3 changes: 2 additions & 1 deletion src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ export { debug, makeDebugger } from './debug'
export eventStack from './eventStack'

export * from './factories'
export getUnhandledProps from './getUnhandledProps'
export { forwardRefFactory } from './forwardRefFactory'
export getElementType from './getElementType'
export getUnhandledProps from './getUnhandledProps'

export {
htmlInputAttrs,
Expand Down