Skip to content

Commit

Permalink
Release 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Klimashkin committed May 18, 2018
1 parent 429ac92 commit d52d620
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 2 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
charset = utf-8

indent_style = space
indent_size = 2

end_of_line = lf
insert_final_newline = false
trim_trailing_whitespace = true
32 changes: 32 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# OS-specific files
.DS_Store
Thumbs.db

# IntelliJ
/.idea/
# VSCode
/.vscode/

# Dependency directories
node_modules/
jspm_packages/

# dist folder, users can get build files on unpkg
dist/

# Optional npm cache directory
.npm

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Optional REPL history
.node_repl_history
# dotenv environment variables file
.env
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 Paul Klimashkin
Copyright (c) 2018 Pavel Klimashkin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
82 changes: 81 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,82 @@
# react-forwardref-utils
Utils to help with React 16.3+ forwardRef method
Utils to help with React 16.3+ [forwardRef](https://reactjs.org/docs/forwarding-refs.html) method

```bash
$ npm install --save react-forwardref-utils
```

## Usage

```js
import {
isForwardRef, forwardRefSymbol, forwardRefFactory, withForwardRef,
} from 'react-forwardref-utils';
```

### `isForwardRef`
Tests if Component is produced by forwardRef function

```js
import {isForwardRef} from 'react-forwardref-utils';

const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));

isForwardRef(FancyButton); // true

```

### `forwardRefSymbol`
Symbol (currently just a string) that points to the ref passed to a component that wrapped with `forwardRefFactory`.

### `forwardRefFactory(Component, [options])`
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.

```js
import {forwardRefFactory, forwardRefSymbol} from 'react-forwardref-utils';

class Button extends React.Component {
render () {
const {[forwardRefSymbol]: ref, ...props} = this.props;

props.ref = ref;
props.className = "FancyButton";

return <button {...props}>{props.children}</button>;
}
}

export default forwardRefFactory(Button/*, {displayName, hoistSource, hoistExclude}*/);
```
###### Options:
- `displayName` *(String)* Name of the result component that will be used in devtools. Will be taken from source component if omitted.
- `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.
- `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).

### `withForwardRef([options])`
Decorator that wraps decorated component with `forwardRefFactory`.

```js
import {withForwardRef, forwardRefSymbol} from 'react-forwardref-utils';

@withForwardRef()
export default class Button extends React.Component {
render () {
const {[forwardRefSymbol]: ref, ...props} = this.props;

props.ref = ref;
props.className = "FancyButton";

return <button {...props}>{props.children}</button>;
}
}
```


## License
[MIT](https://github.com/klimashkin/react-forwardref-utils/blob/master/LICENSE)

[LICENSE file]: https://github.com/klimashkin/react-forwardref-utils/blob/master/LICENSE
64 changes: 64 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "react-forwardref-utils",
"version": "1.0.0",
"description": "Utils to help with React 16.3+ forwardRef method",
"main": "index.js",
"author": "Pavel Klimashkin",
"repository": {
"type": "git",
"url": "git+https://github.com/klimashkin/react-forwardref-utils.git"
},
"keywords": [
"react",
"forwardref"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/klimashkin/react-forwardref-utils/issues"
},
"homepage": "https://github.com/klimashkin/react-forwardref-utils#readme",
"dependencies": {
"hoist-non-react-statics": "^2.5.0"
},
"peerDependencies": {
"react": "^16.3.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-trailing-function-commas": "^6.22.0",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"cross-env": "^5.1.4",
"github-changes": "^1.1.2",
"null-loader": "^0.1.1",
"react": "^16.3.0",
"react-dom": "^16.3.0",
"rimraf": "^2.6.2",
"uglifyjs-webpack-plugin": "^1.2.5",
"webpack": "^4.8.3",
"webpack-cli": "^2.1.3"
},
"scripts": {
"preversion": "npm run clean && npm run build",
"postversion": "npm run changelog",
"build:umd": "cross-env BUILD_MODE=umd webpack",
"build:umd-min": "cross-env BUILD_MODE=umd-min webpack",
"build:es6": "cross-env BUILD_MODE=es2015 webpack",
"build:es6-min": "cross-env BUILD_MODE=es2015-min webpack",
"build:es8": "cross-env BUILD_MODE=es2017 webpack",
"build:es8-min": "cross-env BUILD_MODE=es2017-min webpack",
"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",
"clean": "rimraf dist",
"changelog": "github-changes -o klimashkin -r react-forwardref-utils -b master -f ./CHANGELOG.md --order-semver --use-commit-body"
},
"engines": {
"node": ">=6.0.0"
}
}
57 changes: 57 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, {forwardRef} from 'react';
import hoistStatics from 'hoist-non-react-statics';

/**
* Tests if Component is produced by forwardRef function
* @param Component
* @returns {boolean}
*/
export const isForwardRef = Component => Component.$$typeof && typeof Component.render === 'function';

/**
* 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__';

/**
* 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 {Object|Function} Component - Component to wrap with forwardRef
* @param {Object} [options] - Optional parameters
* @param {String} [options.displayName] - Name of the result component that will be used in devtools.
* Will be taken from source component if omitted
* @param {Object|Function} [options.hoistSource] - 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
* @param {Object} [options.hoistExclude] - 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
* @returns {Object}
*/
export const forwardRefFactory = (
Component,
{displayName, hoistSource = Component, hoistExclude = {$$typeof: true, render: true}} = {}
) => {
const forwardFn = (props, ref) => <Component {...{[forwardRefSymbol]: ref, ...props}}/>;

forwardFn.displayName = displayName || Component.displayName || Component.name;

const forwarder = forwardRef(forwardFn);

hoistStatics(forwarder, hoistSource, hoistExclude);

return forwarder;
};

/**
* Simple HOC that uses forwardRefFactory
* @param [options] - Options for forwardRefFactory
* @returns {Object}
*/
export function withForwardRef(options) {
return Component => forwardRefFactory(Component, options);
}
125 changes: 125 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
const path = require('path');

let babelOptions;
let filename = '[name].js';
const minify = process.env.BUILD_MODE.endsWith('min');

if (!process.env.BUILD_MODE.startsWith('umd')) {
// To see es* prefix in webpack stat output, concatenate folder with filename
filename = `${process.env.BUILD_MODE.match(/^([^-]+)/)[1]}/${filename}`;
}

if (process.env.BUILD_MODE.startsWith('umd')) {
babelOptions = {
presets: [
'react',
'env',
],
plugins: [
'transform-class-properties',
'transform-object-rest-spread',
['transform-react-remove-prop-types', {mode: 'remove'}],
],
};
} else if (process.env.BUILD_MODE.startsWith('es2015')) {
babelOptions = {
plugins: [
['transform-react-jsx', {useBuiltIns: true}],
'syntax-jsx',

'transform-async-to-generator',

'transform-class-properties',
['transform-object-rest-spread', {useBuiltIns: true}],
['transform-react-remove-prop-types', {mode: 'remove'}],
],
};
} else if (process.env.BUILD_MODE.startsWith('es2017')) {
babelOptions = {
plugins: [
['transform-react-jsx', {useBuiltIns: true}],
'syntax-jsx',

'transform-class-properties',
['transform-object-rest-spread', {useBuiltIns: true}],
['transform-react-remove-prop-types', {mode: 'remove'}],
],
};
}

module.exports = {
entry: {
[`react-forwardref-utils${minify ? '.min' : ''}`]: './src/index.js',
},
output: {
filename,
sourceMapFilename: `${filename}.map`,
path: path.resolve(__dirname, 'dist'),
pathinfo: false,
libraryTarget: 'umd',
library: 'SizeWatcher',
},
devtool: 'source-map',
mode: 'production',
optimization: {
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
flagIncludedChunks: true,
occurrenceOrder: true,
providedExports: true,
usedExports: true,
sideEffects: true,
concatenateModules: true,
splitChunks: false,
runtimeChunk: false,
noEmitOnErrors: true,
namedModules: true,
namedChunks: true,
nodeEnv: 'production',
minimize: minify,
},
resolve: {
modules: [
path.resolve('src'),
'node_modules',
],
},
externals: {
'react': 'umd react',
'hoist-non-react-statics': {
amd: 'hoist-non-react-statics',
root: 'hoistNonReactStatics',
commonjs: 'hoist-non-react-statics',
commonjs2: 'hoist-non-react-statics',
},
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: babelOptions,
},
},
],
},
node: {
process: false,
setImmediate: false,
},
stats: {
assets: true,
colors: true,
errors: true,
errorDetails: true,
hash: false,
timings: true,
version: true,
warnings: true,
entrypoints: false,
modules: false,
},
};

0 comments on commit d52d620

Please sign in to comment.