├── .babelrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── initTests.js ├── package-lock.json ├── package.json ├── reacts ├── 0.14.9 │ ├── index.js │ └── package.json ├── 15.0 │ ├── index.js │ └── package.json ├── 15.1 │ ├── index.js │ └── package.json ├── 15.2 │ ├── index.js │ └── package.json ├── 15.3 │ ├── index.js │ └── package.json ├── 15.4 │ ├── index.js │ └── package.json ├── 15.5 │ ├── index.js │ └── package.json ├── 15.6 │ ├── index.js │ └── package.json ├── 16.0 │ ├── index.js │ └── package.json ├── 16.1 │ ├── index.js │ └── package.json ├── 16.2 │ ├── index.js │ └── package.json └── 16.3 │ ├── index.js │ └── package.json ├── src ├── RefForwarder.js ├── __tests__ │ ├── createRef.test.js │ ├── forwardRef.test.js │ └── getRef.test.js ├── createRef.js ├── forwardRef.js ├── getRef.js └── index.js └── test-utils ├── installReactVersions.js └── withReact.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "loose": true, 7 | "targets": { 8 | "browsers": [ 9 | "last 2 versions", 10 | "ie >= 9" 11 | ] 12 | } 13 | } 14 | ], 15 | "react", 16 | "flow" 17 | ], 18 | "plugins": [ 19 | "transform-class-properties", 20 | "add-module-exports" 21 | ] 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | lib 61 | .DS_Store 62 | 63 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present Justin Hines 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-react-ref 2 | 3 | ## What is this? 4 | 5 | React version 16.3 introduces 2 new APIs, `React.createRef` ([React RFC #17](https://github.com/reactjs/rfcs/blob/master/text/0017-new-create-ref.md)) and `React.forwardRef` ([React RFC #30](https://github.com/reactjs/rfcs/blob/master/text/0030-ref-forwarding.md)). 6 | 7 | This lib was created to allow using the new ref APIs without an immediate upgrade. Once upgraded to React 16.3, you should be able to remove this lib from your imports and just import React's version. However, this lib also checks for React's version and, if it is installed, it will use it instead of the polyfilled version. This way, you can remove the polyfill when you're ready and not at the same time that you upgrade. 8 | 9 | #### This project is not recommended for libraries if you intend to expose the component whose ref is fowarded. The reason is that if your users use a version of React before 16.3, they will receive the internal `RefForwarder` component of this package. This package was created to allow users to use the new API as long as the user is the creator and consumer of the forwarding ref. While there is a method in this lib that will always grab the correct ref `getRef`, it is not a good idea to ask your users to depend on and use this package. If you want to forward refs, you may want to fallback to using an additional prop, for example `inputRef`. 10 | 11 | ## How to install 12 | 13 | NPM: 14 | 15 | ``` 16 | npm install create-react-ref 17 | ``` 18 | 19 | YARN: 20 | 21 | ``` 22 | yarn add create-react-ref 23 | ``` 24 | 25 | You'll need to also have `react` installed 26 | 27 | ## API and examples 28 | 29 | ### createRef() 30 | 31 | The `createRef` API returns an object which attaches the ref to a `current` property. The polyfill works by returning a function which when invoked internall by React with the ref, will attach it to a `current` property or the function. 32 | 33 | ```javascript 34 | import createRef from 'create-react-ref/lib/createRef'; 35 | 36 | class MyComponent extends React.Component { 37 | // Once input ref is mounted, it is accessed 38 | // under the `current` proprty 39 | inputRef = createRef(); 40 | 41 | render() { 42 | return ( 43 |
44 | 45 |
46 | ); 47 | } 48 | 49 | componentDidMount() { 50 | this.inputRef.current.focus(); 51 | } 52 | } 53 | ``` 54 | 55 | ### forwardRef(render) 56 | 57 | The `forwardRef` API allows forwarding refs to a child (inner) component when a ref is attached to the parent (outer) component. 58 | Arguments 59 | 60 | * [`render(props, ref)`: `ReactElement`]: Render should be a function that when called returns a ReactElement to render. It gets passed the current `props` and the `ref` to foward. Attach the `ref` to the inner child component's ref prop that you want a user to receive when attaching a ref to the outer component. 61 | 62 | ```javascript 63 | import forwardRef from 'create-react-ref/lib/forwardRef'; 64 | import createRef from 'create-react-ref/lib/createRef'; 65 | 66 | const ThemeContext = React.createContext('light'); 67 | 68 | // Example HOC 69 | function withTheme(ThemedComponent) { 70 | function ThemeContextInjector(props) { 71 | return ( 72 | 73 | {value => ( 74 | 75 | )} 76 | 77 | ); 78 | } 79 | 80 | // Forward refs through to the inner, "themed" component: 81 | return forwardRef((props, ref) => ( 82 | 83 | )); 84 | } 85 | 86 | const ThemedButton = withTheme(Button); 87 | // For the polyfilled forwardRef, you must use `createRef`. 88 | const buttonRef = createRef(); 89 | 90 | // buttonRef.current will point to ThemedButton, rather than ThemeContextInjector 91 | ; 92 | ``` 93 | 94 | ### Caveats 95 | 96 | The polyfilled `forwardRef` is only compatible with refs created from `createRef` and not compatible with `ref` callbacks/functions. If you attach a ref callback to a component returned from the polyfilled `forwardRef`, you will get a RefForwarder component instance. This is one instance of how this library differs from React's implementation. React actually built an internal type to handle this, which cannot be polyfilled, and returns the actual forwared child. However, this polyfill provides a `getRef` function you can use to make sure the correct ref is always returned (polyfilled or not). 97 | 98 | ## Extra APIs not in React 99 | 100 | ### getRef(ref) 101 | 102 | Arguments 103 | 104 | * [`ref`: `Node | Instance | null`]: Use this function to get the actual ref from a ref object created by `createRef` or `React.createRef`. 105 | 106 | Example: 107 | 108 | Using with `createRef` 109 | 110 | ```javascript 111 | class { 112 | divRef = createRef(); 113 | 114 | componentDidMount() { 115 | // When using React.createRef or polyfilled createRef, 116 | // to get the actual div dom node, you have to access 117 | // it on the `current` property. 118 | // For example, this.divRef.current === getRef(this.divRef); 119 | const node = getRef(this.divRef); // returns div node 120 | } 121 | render() { 122 | return
text
123 | } 124 | } 125 | ``` 126 | 127 | Using in a ref callback 128 | 129 | ```javascript 130 | const ForwardingRefComponent = forwardRef((props, ref) => { 131 | return
{props.children}
132 | }); 133 | 134 | class { 135 | handleRef = (node) => { 136 | // If using the polyfilled `forwardRef`: 137 | // node instanceof RefForwarder === true 138 | const actualForwardedNode = getRef(node); 139 | // If using React.forwardRef 140 | // node instanceof HTMLDivElement === true 141 | const actualForwardedNode = node; // || getRef(node); 142 | } 143 | 144 | render() { 145 | return text 146 | } 147 | } 148 | ``` 149 | -------------------------------------------------------------------------------- /initTests.js: -------------------------------------------------------------------------------- 1 | const installReact = require('./test-utils/installReactVersions'); 2 | const path = require('path'); 3 | 4 | installReact(path.resolve('./reacts')); 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-ref", 3 | "version": "0.1.0", 4 | "description": 5 | "Polyfill for the proposed React.createRef and React.forwardRef API", 6 | "main": "lib/index.js", 7 | "license": "MIT", 8 | "keywords": ["react", "polyfill", "ponyfill", "createRef", "forwardRef"], 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/soupaJ/create-react-ref.git" 12 | }, 13 | "files": ["lib"], 14 | "scripts": { 15 | "test": "node ./initTests.js && jest", 16 | "flow": "flow", 17 | "format": "prettier --write '**/*.{js,md,json,js.flow,d.ts}'", 18 | "build": "babel src -d lib --copy-files --ignore __tests__", 19 | "prepublish": "npm run build", 20 | "commit": "lint-staged" 21 | }, 22 | "peerDependencies": { 23 | "react": "^0.14.0 || ^15.0.0 || ^16.0.0" 24 | }, 25 | "devDependencies": { 26 | "babel-cli": "^6.26.0", 27 | "babel-plugin-add-module-exports": "^0.2.1", 28 | "babel-plugin-transform-class-properties": "^6.24.1", 29 | "babel-preset-env": "^1.6.1", 30 | "babel-preset-react": "^6.24.1", 31 | "husky": "^0.14.3", 32 | "jest": "^21.2.1", 33 | "lint-staged": "^6.0.0", 34 | "prettier": "^1.9.1", 35 | "prop-types": "^15.6.0", 36 | "raf": "^3.4.0", 37 | "react": "^16.2.0", 38 | "react-dom": "^16.2.0", 39 | "rollup": "^0.57.1" 40 | }, 41 | "lint-staged": { 42 | "*.{js,md,json,js.flow,d.ts}": ["prettier --write", "git add"] 43 | }, 44 | "jest": { 45 | "silent": true 46 | }, 47 | "dependencies": { 48 | "fbjs": "^0.8.16" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /reacts/0.14.9/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/0.14.9/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^0.14.9", 5 | "react-dom": "^0.14.9" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reacts/15.0/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-addons-test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^15.0.2", 5 | "react-addons-test-utils": "^15.0.2", 6 | "react-dom": "^15.0.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /reacts/15.1/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-addons-test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^15.1.0", 5 | "react-addons-test-utils": "^15.1.0", 6 | "react-dom": "^15.1.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /reacts/15.2/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-addons-test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^15.2.1", 5 | "react-addons-test-utils": "^15.2.1", 6 | "react-dom": "^15.2.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /reacts/15.3/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-addons-test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^15.3.2", 5 | "react-addons-test-utils": "^15.3.2", 6 | "react-dom": "^15.3.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /reacts/15.4/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-addons-test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^15.4.2", 5 | "react-addons-test-utils": "^15.4.2", 6 | "react-dom": "^15.4.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /reacts/15.5/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^15.5.4", 5 | "react-dom": "^15.5.4" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reacts/15.6/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/15.6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "15.6", 5 | "react-dom": "15.6" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reacts/16.0/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/16.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^16.0.0", 5 | "react-dom": "^16.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reacts/16.1/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/16.1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^16.1.1", 5 | "react-dom": "^16.1.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reacts/16.2/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/16.2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "16.2", 5 | "react-dom": "16.2" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reacts/16.3/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | React: require('react'), 3 | ReactDOM: require('react-dom'), 4 | ReactDOMServer: require('react-dom/server'), 5 | TestUtils: require('react-dom/test-utils') 6 | }; 7 | -------------------------------------------------------------------------------- /reacts/16.3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "react": "^16.3.0-alpha.3", 5 | "react-dom": "^16.3.0-alpha.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/RefForwarder.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import createRef from './createRef'; 3 | 4 | export default class ForwardRefPolyfill extends React.Component { 5 | static displayName = 'ForwardRefPolyfill'; 6 | 7 | constructor(props) { 8 | super(props); 9 | this.__forwardedRef = createRef(); 10 | } 11 | 12 | __render() { 13 | return null; 14 | } 15 | 16 | getRef() { 17 | // Check for `.current` first before `.value` since it's the 18 | // newest property name in React. 19 | return this.__forwardedRef.current || this.__forwardedRef.value; 20 | } 21 | 22 | render() { 23 | return this.__render(this.props, this.__forwardedRef); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/__tests__/createRef.test.js: -------------------------------------------------------------------------------- 1 | import withReact from '../../test-utils/withReact'; 2 | 3 | const name = 'cjs'; 4 | 5 | describe(`create-react-ref (${name})`, () => { 6 | withReact(({ React, ReactDOM, TestUtils, version }) => { 7 | describe(`react@${version}`, () => { 8 | let createRef; 9 | 10 | beforeEach(() => { 11 | jest.resetModules(); 12 | jest.setMock('react', React); 13 | jest.setMock('react-dom', ReactDOM); 14 | createRef = require('../createRef'); 15 | }); 16 | 17 | it('should create a function with a current property if polyfilled', () => { 18 | const ref = createRef(); 19 | 20 | if (React.createRef) { 21 | expect(typeof ref).toEqual('object'); 22 | } else { 23 | expect(typeof ref).toEqual('function'); 24 | } 25 | expect(ref.current).toEqual(null); 26 | }); 27 | 28 | // Borrowed from React's tests 29 | it('should support object-style refs', () => { 30 | const innerObj = {}; 31 | const outerObj = {}; 32 | 33 | class Wrapper extends React.Component { 34 | getObject = () => { 35 | return this.props.object; 36 | }; 37 | 38 | render() { 39 | return
{this.props.children}
; 40 | } 41 | } 42 | 43 | let mounted = false; 44 | 45 | class Component extends React.Component { 46 | constructor() { 47 | super(); 48 | this.innerRef = createRef(); 49 | this.outerRef = createRef(); 50 | } 51 | render() { 52 | const inner = ; 53 | const outer = ( 54 | 55 | {inner} 56 | 57 | ); 58 | return outer; 59 | } 60 | 61 | componentDidMount() { 62 | expect(this.innerRef.current.getObject()).toEqual(innerObj); 63 | expect(this.outerRef.current.getObject()).toEqual(outerObj); 64 | mounted = true; 65 | } 66 | } 67 | 68 | TestUtils.renderIntoDocument(); 69 | expect(mounted).toBe(true); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/__tests__/forwardRef.test.js: -------------------------------------------------------------------------------- 1 | import withReact from '../../test-utils/withReact'; 2 | const name = 'cjs'; 3 | 4 | describe(`create-react-ref (${name})`, () => { 5 | withReact(({ React, ReactDOM, TestUtils, version }) => { 6 | describe(`react@${version}`, () => { 7 | let createRef; 8 | let forwardRef; 9 | 10 | beforeEach(() => { 11 | jest.resetModules(); 12 | jest.setMock('react', React); 13 | jest.setMock('react-dom', ReactDOM); 14 | createRef = require('../createRef'); 15 | forwardRef = require('../forwardRef'); 16 | }); 17 | // Borrowed from React's ref tests 18 | // https://github.com/facebook/react/blob/bc70441c8b3fa85338283af3eeb47b5d15e9dbfe/packages/react/src/__tests__/forwardRef-test.internal.js 19 | it('should work without a ref to be forwarded', () => { 20 | class Child extends React.Component { 21 | render() { 22 | expect(this.props.value).toEqual(123); 23 | return null; 24 | } 25 | } 26 | 27 | function Wrapper(props) { 28 | return ; 29 | } 30 | 31 | const RefForwardingComponent = forwardRef((props, ref) => ( 32 | 33 | )); 34 | 35 | TestUtils.renderIntoDocument(); 36 | }); 37 | 38 | it('should forward a ref for a single child', () => { 39 | class Child extends React.Component { 40 | render() { 41 | expect(this.props.value).toEqual(123); 42 | return null; 43 | } 44 | } 45 | 46 | function Wrapper(props) { 47 | return ; 48 | } 49 | 50 | const RefForwardingComponent = forwardRef((props, ref) => ( 51 | 52 | )); 53 | 54 | const ref = createRef(); 55 | TestUtils.renderIntoDocument( 56 | 57 | ); 58 | 59 | expect(ref.current instanceof Child).toBe(true); 60 | }); 61 | 62 | it('should forward a ref for multiple children', () => { 63 | class Child extends React.Component { 64 | render() { 65 | expect(this.props.value).toEqual(123); 66 | return null; 67 | } 68 | } 69 | 70 | function Wrapper(props) { 71 | return ; 72 | } 73 | 74 | const RefForwardingComponent = forwardRef((props, ref) => ( 75 | 76 | )); 77 | 78 | const ref = createRef(); 79 | 80 | TestUtils.renderIntoDocument( 81 |
82 |
83 | 84 |
85 |
86 | ); 87 | 88 | expect(ref.current instanceof Child).toBe(true); 89 | }); 90 | 91 | it('should update refs when switching between children', () => { 92 | function FunctionalComponent({ forwardedRef, setRefOnDiv }) { 93 | return ( 94 |
95 |
First
96 | Second 97 |
98 | ); 99 | } 100 | 101 | const RefForwardingComponent = forwardRef((props, ref) => ( 102 | 103 | )); 104 | 105 | const ref = createRef(); 106 | 107 | TestUtils.renderIntoDocument( 108 | 109 | ); 110 | expect(ref.current.tagName.toLowerCase()).toBe('div'); 111 | 112 | TestUtils.renderIntoDocument( 113 | 114 | ); 115 | expect(ref.current.tagName.toLowerCase()).toBe('span'); 116 | }); 117 | 118 | it('should maintain child instance and ref through updates', () => { 119 | // TODO: Make tests work with async rendering 120 | let passedProp; 121 | class Child extends React.Component { 122 | constructor(props) { 123 | super(props); 124 | } 125 | render() { 126 | passedProp = this.props.value; 127 | return null; 128 | } 129 | } 130 | 131 | function Wrapper(props) { 132 | return ; 133 | } 134 | 135 | const RefForwardingComponent = forwardRef((props, ref) => ( 136 | 137 | )); 138 | 139 | let setRefCount = 0; 140 | let _ref; 141 | 142 | const _createRef = createRef(); 143 | const ref = function(r) { 144 | setRefCount++; 145 | 146 | if (React.createRef) { 147 | _ref = r; 148 | } else { 149 | _createRef(r); 150 | _ref = _createRef.current; 151 | } 152 | }; 153 | 154 | const div = document.createElement('div'); 155 | 156 | ReactDOM.render(, div); 157 | expect(passedProp).toEqual(123); 158 | expect(_ref instanceof Child).toBe(true); 159 | expect(setRefCount).toBe(1); 160 | ReactDOM.render(, div); 161 | expect(passedProp).toEqual(456); 162 | expect(_ref instanceof Child).toBe(true); 163 | expect(setRefCount).toBe(1); 164 | }); 165 | 166 | it('should support rendering null', () => { 167 | const RefForwardingComponent = forwardRef((props, ref) => null); 168 | 169 | const ref = createRef(); 170 | 171 | TestUtils.renderIntoDocument(); 172 | expect(ref.current).toBe(null); 173 | }); 174 | 175 | it('should support rendering null for multiple children', () => { 176 | const RefForwardingComponent = forwardRef((props, ref) => null); 177 | 178 | const ref = createRef(); 179 | 180 | TestUtils.renderIntoDocument( 181 |
182 |
183 | 184 |
185 |
186 | ); 187 | expect(ref.current).toBe(null); 188 | }); 189 | 190 | it('should warn if not provided a callback during creation', () => { 191 | const error = console.error; 192 | console.error = jest.fn(); 193 | //jest.spyOn(console, 'error'); 194 | forwardRef(); 195 | expect(console.error).toHaveBeenLastCalledWith( 196 | 'Warning: forwardRef requires a render function but was given undefined.' 197 | ); 198 | forwardRef(undefined); 199 | expect(console.error).toHaveBeenLastCalledWith( 200 | 'Warning: forwardRef requires a render function but was given undefined.' 201 | ); 202 | forwardRef(null); 203 | expect(console.error).toHaveBeenLastCalledWith( 204 | 'Warning: forwardRef requires a render function but was given null.' 205 | ); 206 | forwardRef('foo'); 207 | expect(console.error).toHaveBeenLastCalledWith( 208 | 'Warning: forwardRef requires a render function but was given string.' 209 | ); 210 | console.error.mockClear(); 211 | console.error = error; 212 | }); 213 | 214 | if (React.forwardRef) { 215 | it('should use React.forwardRef if found', () => { 216 | expect(React.forwardRef).toEqual(forwardRef); 217 | }); 218 | } 219 | }); 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /src/__tests__/getRef.test.js: -------------------------------------------------------------------------------- 1 | import withReact from '../../test-utils/withReact'; 2 | const name = 'cjs'; 3 | 4 | describe(`create-react-ref (${name})`, () => { 5 | withReact(({ React, ReactDOM, TestUtils, version }) => { 6 | describe(`react@${version}`, () => { 7 | let createRef; 8 | let forwardRef; 9 | let getRef; 10 | 11 | beforeEach(() => { 12 | jest.resetModules(); 13 | jest.setMock('react', React); 14 | jest.setMock('react-dom', ReactDOM); 15 | getRef = require('../getRef'); 16 | createRef = require('../createRef'); 17 | forwardRef = require('../forwardRef'); 18 | }); 19 | 20 | it('should do return the ref from the createRef object', () => { 21 | let ref = createRef(); 22 | const Component = props =>
; 23 | 24 | TestUtils.renderIntoDocument(); 25 | expect(getRef(ref).tagName.toLowerCase()).toBe('div'); 26 | }); 27 | 28 | it('should return null if not passed a function or object', () => { 29 | expect(getRef()).toBe(null); 30 | expect(getRef(null)).toBe(null); 31 | }); 32 | 33 | it('should return the ref from the ref callback', () => { 34 | const Component = props => ( 35 |
{ 37 | expect(getRef(ref).tagName.toLowerCase()).toBe('div'); 38 | }} 39 | /> 40 | ); 41 | 42 | TestUtils.renderIntoDocument(); 43 | }); 44 | 45 | it('should warn if not provided a ref object or inside a ref callback', () => { 46 | const error = console.error; 47 | console.error = jest.fn(); 48 | getRef(() => {}); 49 | expect(console.error).toHaveBeenLastCalledWith( 50 | 'Warning: getRef: It looks like you may have passed `getRef` the ref ' + 51 | 'callback as an argument. `getRef` should be used with a ref object ' + 52 | 'created by `createRef` or inside a ref callback.' 53 | ); 54 | console.error.mockClear(); 55 | const ref = getRef(createRef()); 56 | expect(console.error).not.toBeCalled(); 57 | expect(ref).toEqual(null); 58 | console.error = error; 59 | }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/createRef.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import getRef from './getRef'; 3 | 4 | export default React.createRef || 5 | function createRef() { 6 | function ref(instanceOrNode) { 7 | ref.current = getRef(instanceOrNode) || null; 8 | } 9 | 10 | ref.current = null; 11 | 12 | if (process.env.NODE_ENV !== 'production') { 13 | Object.seal(ref); 14 | } 15 | 16 | return ref; 17 | }; 18 | -------------------------------------------------------------------------------- /src/forwardRef.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import createRef from './createRef'; 3 | import RefForwarder from './RefForwarder'; 4 | import warning from 'fbjs/lib/warning'; 5 | 6 | export default React.forwardRef || 7 | function forwardRef(render) { 8 | if (process.env.NODE_ENV !== 'production') { 9 | warning( 10 | typeof render === 'function', 11 | 'forwardRef requires a render function but was given %s.', 12 | render === null ? 'null' : typeof render 13 | ); 14 | } 15 | return class extends RefForwarder { 16 | __render = render; 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/getRef.js: -------------------------------------------------------------------------------- 1 | import { FORWARD_REF_KEY } from './forwardRef'; 2 | import RefForwarder from './RefForwarder'; 3 | import warning from 'fbjs/lib/warning'; 4 | 5 | export default function getRef(refObject) { 6 | if (!refObject) { 7 | return null; 8 | } 9 | 10 | let ref = refObject; 11 | 12 | if (typeof ref === 'function' && process.env.NODE_ENV !== 'production') { 13 | warning( 14 | ref.hasOwnProperty('current'), 15 | 'getRef: It looks like you may have passed `getRef` the ref callback as ' + 16 | 'an argument. `getRef` should be used with a ref object created by ' + 17 | '`createRef` or inside a ref callback.' 18 | ); 19 | } 20 | 21 | if (Object.keys(ref).length === 1) { 22 | if (ref.hasOwnProperty('current')) { 23 | ref = ref.current; 24 | // We probably don't have to support this route since it was only 25 | // in one version of React and it was an alpha release (16.3.0-alpha.1). 26 | } else if (ref.hasOwnProperty('value')) { 27 | ref = ref.value; 28 | } 29 | } 30 | 31 | // Get polyfilled forwardedRef, if it exists 32 | if (ref instanceof RefForwarder) { 33 | ref = ref.getRef(); 34 | } 35 | 36 | return ref; 37 | } 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as createRef } from './createRef'; 2 | export { default as forwardRef } from './forwardRef'; 3 | export { default as getRef } from './getRef'; 4 | -------------------------------------------------------------------------------- /test-utils/installReactVersions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { readdirSync } = require('fs'); 4 | const { join } = require('path'); 5 | const { spawnSync } = require('child_process'); 6 | const fs = require('fs'); 7 | 8 | module.exports = function(path) { 9 | return readdirSync(path).forEach(version => { 10 | const cwd = join(path, version); 11 | 12 | // try { 13 | // fs.statSync(join(cwd, 'node_modules')); 14 | // } catch(ex) { 15 | console.info(`Installing React version ${version} ...`); 16 | const spawn = spawnSync('npm', ['install'], { 17 | cwd, 18 | stdio: 'inherit' 19 | }); 20 | 21 | if (spawn.status > 0) { 22 | process.exit(spawn.status); 23 | } 24 | // } 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /test-utils/withReact.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const { readdirSync } = require('fs'); 3 | const reacts = {}; 4 | 5 | require('raf/polyfill'); 6 | 7 | const reactsPath = join(__dirname, '../reacts'); 8 | readdirSync(reactsPath).forEach(version => { 9 | const cwd = join(reactsPath, version); 10 | reacts[version] = { 11 | ...require(cwd), 12 | version 13 | }; 14 | }); 15 | 16 | module.exports = function withReact(fn) { 17 | Object.keys(reacts).forEach(version => { 18 | fn(reacts[version]); 19 | }); 20 | }; 21 | --------------------------------------------------------------------------------