Skip to content

Commit d52d620

Browse files
author
Paul Klimashkin
committed
Release 1.0.0
1 parent 429ac92 commit d52d620

File tree

8 files changed

+372
-2
lines changed

8 files changed

+372
-2
lines changed

.editorconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
6+
indent_style = space
7+
indent_size = 2
8+
9+
end_of_line = lf
10+
insert_final_newline = false
11+
trim_trailing_whitespace = true

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# OS-specific files
2+
.DS_Store
3+
Thumbs.db
4+
5+
# IntelliJ
6+
/.idea/
7+
# VSCode
8+
/.vscode/
9+
10+
# Dependency directories
11+
node_modules/
12+
jspm_packages/
13+
14+
# dist folder, users can get build files on unpkg
15+
dist/
16+
17+
# Optional npm cache directory
18+
.npm
19+
20+
# Output of 'npm pack'
21+
*.tgz
22+
23+
# Yarn Integrity file
24+
.yarn-integrity
25+
26+
# Directory for instrumented libs generated by jscoverage/JSCover
27+
lib-cov
28+
29+
# Optional REPL history
30+
.node_repl_history
31+
# dotenv environment variables file
32+
.env

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2018 Paul Klimashkin
3+
Copyright (c) 2018 Pavel Klimashkin
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,82 @@
11
# react-forwardref-utils
2-
Utils to help with React 16.3+ forwardRef method
2+
Utils to help with React 16.3+ [forwardRef](https://reactjs.org/docs/forwarding-refs.html) method
3+
4+
```bash
5+
$ npm install --save react-forwardref-utils
6+
```
7+
8+
## Usage
9+
10+
```js
11+
import {
12+
isForwardRef, forwardRefSymbol, forwardRefFactory, withForwardRef,
13+
} from 'react-forwardref-utils';
14+
```
15+
16+
### `isForwardRef`
17+
Tests if Component is produced by forwardRef function
18+
19+
```js
20+
import {isForwardRef} from 'react-forwardref-utils';
21+
22+
const FancyButton = React.forwardRef((props, ref) => (
23+
<button ref={ref} className="FancyButton">
24+
{props.children}
25+
</button>
26+
));
27+
28+
isForwardRef(FancyButton); // true
29+
30+
```
31+
32+
### `forwardRefSymbol`
33+
Symbol (currently just a string) that points to the ref passed to a component that wrapped with `forwardRefFactory`.
34+
35+
### `forwardRefFactory(Component, [options])`
36+
Wraps passed component with `forwardRef`, assigns its statics to result using [hoist-non-react-statics](https://github.com/mridgway/hoist-non-react-statics) and provide special symbol to take ref from parent call.
37+
38+
```js
39+
import {forwardRefFactory, forwardRefSymbol} from 'react-forwardref-utils';
40+
41+
class Button extends React.Component {
42+
render () {
43+
const {[forwardRefSymbol]: ref, ...props} = this.props;
44+
45+
props.ref = ref;
46+
props.className = "FancyButton";
47+
48+
return <button {...props}>{props.children}</button>;
49+
}
50+
}
51+
52+
export default forwardRefFactory(Button/*, {displayName, hoistSource, hoistExclude}*/);
53+
```
54+
###### Options:
55+
- `displayName` *(String)* Name of the result component that will be used in devtools. Will be taken from source component if omitted.
56+
- `hoistSource` *(Object|Function)* Source for taking static methods for assigning them to result component. In case you want to wrap with forwardRef another wrapper of original component, but methods should be copied from original. For instance, when you write HOC and wrap that HOC with forwardRefFactory - statics should be copied from original component, not from the HOC.
57+
- `hoistExclude` *(Object)* Object to exclude some methods from hoisting, third parameter of hoist-non-react-statics module. By default excludes forwardRef component properties in case we wrap another forwardRef component (until https://github.com/mridgway/hoist-non-react-statics/issues/48 is resolved).
58+
59+
### `withForwardRef([options])`
60+
Decorator that wraps decorated component with `forwardRefFactory`.
61+
62+
```js
63+
import {withForwardRef, forwardRefSymbol} from 'react-forwardref-utils';
64+
65+
@withForwardRef()
66+
export default class Button extends React.Component {
67+
render () {
68+
const {[forwardRefSymbol]: ref, ...props} = this.props;
69+
70+
props.ref = ref;
71+
props.className = "FancyButton";
72+
73+
return <button {...props}>{props.children}</button>;
74+
}
75+
}
76+
```
77+
78+
79+
## License
80+
[MIT](https://github.com/klimashkin/react-forwardref-utils/blob/master/LICENSE)
81+
82+
[LICENSE file]: https://github.com/klimashkin/react-forwardref-utils/blob/master/LICENSE

package.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"name": "react-forwardref-utils",
3+
"version": "1.0.0",
4+
"description": "Utils to help with React 16.3+ forwardRef method",
5+
"main": "index.js",
6+
"author": "Pavel Klimashkin",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/klimashkin/react-forwardref-utils.git"
10+
},
11+
"keywords": [
12+
"react",
13+
"forwardref"
14+
],
15+
"license": "MIT",
16+
"bugs": {
17+
"url": "https://github.com/klimashkin/react-forwardref-utils/issues"
18+
},
19+
"homepage": "https://github.com/klimashkin/react-forwardref-utils#readme",
20+
"dependencies": {
21+
"hoist-non-react-statics": "^2.5.0"
22+
},
23+
"peerDependencies": {
24+
"react": "^16.3.0"
25+
},
26+
"devDependencies": {
27+
"babel-cli": "^6.26.0",
28+
"babel-core": "^6.26.3",
29+
"babel-loader": "^7.1.4",
30+
"babel-plugin-syntax-async-functions": "^6.13.0",
31+
"babel-plugin-syntax-trailing-function-commas": "^6.22.0",
32+
"babel-plugin-transform-async-to-generator": "^6.24.1",
33+
"babel-plugin-transform-class-properties": "^6.24.1",
34+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
35+
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
36+
"babel-preset-env": "^1.7.0",
37+
"babel-preset-react": "^6.24.1",
38+
"cross-env": "^5.1.4",
39+
"github-changes": "^1.1.2",
40+
"null-loader": "^0.1.1",
41+
"react": "^16.3.0",
42+
"react-dom": "^16.3.0",
43+
"rimraf": "^2.6.2",
44+
"uglifyjs-webpack-plugin": "^1.2.5",
45+
"webpack": "^4.8.3",
46+
"webpack-cli": "^2.1.3"
47+
},
48+
"scripts": {
49+
"preversion": "npm run clean && npm run build",
50+
"postversion": "npm run changelog",
51+
"build:umd": "cross-env BUILD_MODE=umd webpack",
52+
"build:umd-min": "cross-env BUILD_MODE=umd-min webpack",
53+
"build:es6": "cross-env BUILD_MODE=es2015 webpack",
54+
"build:es6-min": "cross-env BUILD_MODE=es2015-min webpack",
55+
"build:es8": "cross-env BUILD_MODE=es2017 webpack",
56+
"build:es8-min": "cross-env BUILD_MODE=es2017-min webpack",
57+
"build": "npm run build:umd && npm run build:umd-min && npm run build:es6 && npm run build:es6-min && npm run build:es8 && npm run build:es8-min",
58+
"clean": "rimraf dist",
59+
"changelog": "github-changes -o klimashkin -r react-forwardref-utils -b master -f ./CHANGELOG.md --order-semver --use-commit-body"
60+
},
61+
"engines": {
62+
"node": ">=6.0.0"
63+
}
64+
}

src/index.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, {forwardRef} from 'react';
2+
import hoistStatics from 'hoist-non-react-statics';
3+
4+
/**
5+
* Tests if Component is produced by forwardRef function
6+
* @param Component
7+
* @returns {boolean}
8+
*/
9+
export const isForwardRef = Component => Component.$$typeof && typeof Component.render === 'function';
10+
11+
/**
12+
* Use just a string for now (react 16.3), since react doesn't support Symbols in props yet
13+
* https://github.com/facebook/react/issues/7552
14+
* @type {string}
15+
*/
16+
export const forwardRefSymbol = '__forwardRef__';
17+
18+
/**
19+
* Wraps passed component with react 'forwardRef' function, which produce new component with type 'object' and structure like so:
20+
* { $$type: Symbol(), render: function }
21+
* Assigns (hoists) static methods of passed component to result forward component using 'hoist-non-react-statics' module.
22+
*
23+
* @param {Object|Function} Component - Component to wrap with forwardRef
24+
* @param {Object} [options] - Optional parameters
25+
* @param {String} [options.displayName] - Name of the result component that will be used in devtools.
26+
* Will be taken from source component if omitted
27+
* @param {Object|Function} [options.hoistSource] - Source for taking static methods for assigning them to result component.
28+
* In case you want to wrap with forwardRef another wrapper of original component,
29+
* but methods should be copied from original
30+
* @param {Object} [options.hoistExclude] - Object to exclude some methods from hoisting, third parameter of hoist-non-react-statics module.
31+
* By default excludes forwardRef component properties in case we wrap another forwardRef component.
32+
* Until https://github.com/mridgway/hoist-non-react-statics/issues/48 is resolved
33+
* @returns {Object}
34+
*/
35+
export const forwardRefFactory = (
36+
Component,
37+
{displayName, hoistSource = Component, hoistExclude = {$$typeof: true, render: true}} = {}
38+
) => {
39+
const forwardFn = (props, ref) => <Component {...{[forwardRefSymbol]: ref, ...props}}/>;
40+
41+
forwardFn.displayName = displayName || Component.displayName || Component.name;
42+
43+
const forwarder = forwardRef(forwardFn);
44+
45+
hoistStatics(forwarder, hoistSource, hoistExclude);
46+
47+
return forwarder;
48+
};
49+
50+
/**
51+
* Simple HOC that uses forwardRefFactory
52+
* @param [options] - Options for forwardRefFactory
53+
* @returns {Object}
54+
*/
55+
export function withForwardRef(options) {
56+
return Component => forwardRefFactory(Component, options);
57+
}

webpack.config.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
const path = require('path');
2+
3+
let babelOptions;
4+
let filename = '[name].js';
5+
const minify = process.env.BUILD_MODE.endsWith('min');
6+
7+
if (!process.env.BUILD_MODE.startsWith('umd')) {
8+
// To see es* prefix in webpack stat output, concatenate folder with filename
9+
filename = `${process.env.BUILD_MODE.match(/^([^-]+)/)[1]}/${filename}`;
10+
}
11+
12+
if (process.env.BUILD_MODE.startsWith('umd')) {
13+
babelOptions = {
14+
presets: [
15+
'react',
16+
'env',
17+
],
18+
plugins: [
19+
'transform-class-properties',
20+
'transform-object-rest-spread',
21+
['transform-react-remove-prop-types', {mode: 'remove'}],
22+
],
23+
};
24+
} else if (process.env.BUILD_MODE.startsWith('es2015')) {
25+
babelOptions = {
26+
plugins: [
27+
['transform-react-jsx', {useBuiltIns: true}],
28+
'syntax-jsx',
29+
30+
'transform-async-to-generator',
31+
32+
'transform-class-properties',
33+
['transform-object-rest-spread', {useBuiltIns: true}],
34+
['transform-react-remove-prop-types', {mode: 'remove'}],
35+
],
36+
};
37+
} else if (process.env.BUILD_MODE.startsWith('es2017')) {
38+
babelOptions = {
39+
plugins: [
40+
['transform-react-jsx', {useBuiltIns: true}],
41+
'syntax-jsx',
42+
43+
'transform-class-properties',
44+
['transform-object-rest-spread', {useBuiltIns: true}],
45+
['transform-react-remove-prop-types', {mode: 'remove'}],
46+
],
47+
};
48+
}
49+
50+
module.exports = {
51+
entry: {
52+
[`react-forwardref-utils${minify ? '.min' : ''}`]: './src/index.js',
53+
},
54+
output: {
55+
filename,
56+
sourceMapFilename: `${filename}.map`,
57+
path: path.resolve(__dirname, 'dist'),
58+
pathinfo: false,
59+
libraryTarget: 'umd',
60+
library: 'SizeWatcher',
61+
},
62+
devtool: 'source-map',
63+
mode: 'production',
64+
optimization: {
65+
removeAvailableModules: true,
66+
removeEmptyChunks: true,
67+
mergeDuplicateChunks: true,
68+
flagIncludedChunks: true,
69+
occurrenceOrder: true,
70+
providedExports: true,
71+
usedExports: true,
72+
sideEffects: true,
73+
concatenateModules: true,
74+
splitChunks: false,
75+
runtimeChunk: false,
76+
noEmitOnErrors: true,
77+
namedModules: true,
78+
namedChunks: true,
79+
nodeEnv: 'production',
80+
minimize: minify,
81+
},
82+
resolve: {
83+
modules: [
84+
path.resolve('src'),
85+
'node_modules',
86+
],
87+
},
88+
externals: {
89+
'react': 'umd react',
90+
'hoist-non-react-statics': {
91+
amd: 'hoist-non-react-statics',
92+
root: 'hoistNonReactStatics',
93+
commonjs: 'hoist-non-react-statics',
94+
commonjs2: 'hoist-non-react-statics',
95+
},
96+
},
97+
module: {
98+
rules: [
99+
{
100+
test: /\.js$/,
101+
exclude: /node_modules/,
102+
use: {
103+
loader: 'babel-loader',
104+
options: babelOptions,
105+
},
106+
},
107+
],
108+
},
109+
node: {
110+
process: false,
111+
setImmediate: false,
112+
},
113+
stats: {
114+
assets: true,
115+
colors: true,
116+
errors: true,
117+
errorDetails: true,
118+
hash: false,
119+
timings: true,
120+
version: true,
121+
warnings: true,
122+
entrypoints: false,
123+
modules: false,
124+
},
125+
};

0 commit comments

Comments
 (0)