Matt
Matt

Reputation: 8942

Using react external components and webpack

I use webpack and have a simple react application, where I want to use react-autosuggest component. When I want to use this component in my application I get error:

Uncaught Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.

index.jsx

var React = require('react')
var Autosuggest = require('react-autosuggest')

var autoCompleteItems = ['item1', 'item2', 'item3', 'item4', 'item5', 'item6'];

function getSuggestions(input, callback) {
  const escapedInput = utils.escapeRegexCharacters(input.trim());
  const lowercasedInput = input.trim().toLowerCase();
  const suburbMatchRegex = new RegExp('\\b' + escapedInput, 'i');
  const suggestions = autoCompleteItems
    .filter( suburbObj => suburbMatchRegex.test(suburbObj.suburb) )
    .sort( (suburbObj1, suburbObj2) =>
      suburbObj1.suburb.toLowerCase().indexOf(lowercasedInput) -
      suburbObj2.suburb.toLowerCase().indexOf(lowercasedInput)
    )
    .slice(0, 7)
    .map( suburbObj => suburbObj.suburb );

  setTimeout(() => callback(null, suggestions), 300);
}

class SuggestWrapper extends React.Component {
  render () {

    var inputId = 'input-example';

    const inputAttributes = {
      id: inputId,
      className: "form-control",
      defaultValue: '',
      placeholder: this.props.propertyName
    };

    return (

      <Autosuggest suggestions={getSuggestions}
                   inputAttributes={inputAttributes}
                   ref={ () => { document.getElementById(inputId).focus(); } } />

    );
  }
}


class App extends React.Component {
  render () {
    return (
      <div>
        <SuggestWrapper />
      </div>
    );
  }
}

React.render(<App />, document.getElementById('content'));

package.json

{
  "name": "react_modules",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "npm run serve | npm run dev",
    "serve": "./node_modules/.bin/http-server -p 8080",
    "dev": "webpack-dev-server -d --progress --colors --port 8090"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^5.8.24",
    "babel-loader": "^5.3.2",
    "bootstrap": "^3.3.5",
    "bower-webpack-plugin": "^0.1.8",
    "css-loader": "^0.18.0",
    "events": "^1.0.2",
    "extract-text-webpack-plugin": "^0.8.2",
    "file-loader": "^0.8.4",
    "http-server": "^0.8.0",
    "jquery": "^2.1.4",
    "jquery-ui": "^1.10.5",
    "less": "^2.5.1",
    "less-loader": "^2.2.0",
    "lodash": "^3.10.1",
    "node-sass": "^3.3.2",
    "object-assign": "^4.0.1",
    "path": "^0.11.14",
    "react": "^0.13.3",
    "react-autosuggest": "^1.18.3",
    "react-hot-loader": "^1.3.0",
    "sass-loader": "^2.0.1",
    "style-loader": "^0.12.3",
    "svg-sprite-loader": "0.0.3",
    "url-loader": "^0.5.6",
    "webpack": "^1.12.1",
    "webpack-dev-server": "^1.10.1"
  }
}

webpack.config.js

const BowerWebpackPlugin = require("bower-webpack-plugin");

module.exports = {
  entry: './src/index.jsx',
  output: {
    filename: 'bundle.js',
    sourceMapFilename: "[file].map",
    publicPath: 'http://localhost:8090/assets'
  },
  debug: true,
  devtool: 'inline-source-map',
  module: {
    loaders: [{
      test: /\.js[x]?$/,
      loaders: ['react-hot', 'babel'],
      exclude: /node_modules/
    }, {
      test: /\.scss$/,
      loaders: ['style', 'css?sourceMap', 'sass?sourceMap']
    }, {
      test: /\.less$/,
      loaders: ['style', 'css?sourceMap', 'less?sourceMap']
    }, {
      test: /\.css$/,
      loaders: ['style', 'css']
    }, {
      test: /\.woff$/,
      loader: "url-loader?limit=10000&mimetype=application/font-woff"
    }, {
      test: /\.woff2$/,
      loader: "url-loader?limit=10000&mimetype=application/font-woff2"
    }, {
      test: /\.(eot|ttf|svg|gif|png)$/,
      loader: "file-loader"
    }]
  },
  plugins: [
    new BowerWebpackPlugin()
  ],
  externals: {
    'react': 'React'
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  }
}

index.html

<!DOCTYPE html>
<html>
<head>
    <title>App</title>
    <!-- include react -->
    <script src="./node_modules/react/dist/react-with-addons.js"></script>
</head>
<body>
    <div id="content">
        <!-- this is where the root react component will get rendered -->
    </div>
    <!-- include the webpack-dev-server script so our scripts get reloaded when we make a change -->
    <!-- we'll run the webpack dev server on port 8090, so make sure it is correct -->
    <script src="http://localhost:8090/webpack-dev-server.js"></script>
    <!-- include the bundle that contains all our scripts, produced by webpack -->
    <!-- the bundle is served by the webpack-dev-server, so serve it also from localhost:8090 -->
    <script type="text/javascript" src="http://localhost:8090/assets/bundle.js"></script>
</body>
</html>

I tried to follow this post and add:

alias: {
      'react': path.join(__dirname, 'node_modules', 'react')
    },

but it didn't help.

Upvotes: 1

Views: 4002

Answers (2)

Adam DiCarlo
Adam DiCarlo

Reputation: 6993

This error can happen when you have two (or more) copies of React in your bundle. Try running npm ls react to see if this is happening. Then you might try npm dedupe (or upgrade to npm 3.x. It's still beta but it's pretty stable and it dedupes automatically). You really need all the packages you use to be compatible with the version of React you're using, of course, since having multiple Reacts causes errors (and a bloated bundle!).

Edit: just noticed you have a script tag for React and you're importing it. Taking out the react script tag might be all you need to fix the problem!

Upvotes: 5

gcedo
gcedo

Reputation: 4931

The explanation to your error is contained in the error message:

Uncaught Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's render method). Try rendering this component inside of a new top-level component which will hold the ref.

You cannot add a ref to a top level component, as you are doing in your SuggestWrapper:

<Autosuggest suggestions={getSuggestions}
               inputAttributes={inputAttributes}
               ref={ () => { document.getElementById(inputId).focus(); } } />

To achieve what you want, you may use the componentDidMount lifecycle hook:

componentDidMount() {
    document.getElementById(inputId).focus();
}

From the docs:

At this point in the lifecycle, the component has a DOM representation which you can access via React.findDOMNode(this). The componentDidMount() method of child components is invoked before that of parent components.

Upvotes: 3

Related Questions