react-rails icon indicating copy to clipboard operation
react-rails copied to clipboard

Cannot find component (Rails 5.1.2, webpacker, react-rails)

Open andrewcsmith opened this issue 6 years ago • 23 comments

Help us help you! Please choose one:

  • [ ] My app crashes with react-rails, so I've included the stack trace and the exact steps which make it crash.
  • [x] My app doesn't crash, but I'm getting unexpected behavior. So, I've described the unexpected behavior and suggested a new behavior.
  • [ ] I'm trying to use react-rails with another library, but I'm having trouble. I've described my JavaScript management setup (eg, Sprockets, Webpack...), how I'm trying to use this other library, and why it's not working.
  • [ ] I have another issue to discuss.

Essentially the same resulting problem as #757 and #713, where including my (haml) view helper = react_component 'PerformanceRow' gives me the following error messages:

Error: Cannot find module './PerformanceRow'.
    at webpackContextResolve (application.js:4549)
    at webpackContext (application.js:4544)
    at eval (fromRequireContext.js?f05c:13)
    at Object.eval [as getConstructor] (fromRequireContextWithGlobalFallback.js?7c97:13)
    at Object.mountComponents (index.js?0542:82)
    at HTMLDocument.ReactRailsUJS.handleMount (index.js?0542:124)
    at HTMLDocument.dispatch (event.js?268c:338)
    at HTMLDocument.elemData.handle (event.js?268c:146)

fromRequireContextWithGlobalFallback.js?7c97:20 ReferenceError: PerformanceRow is not defined
    at eval (eval at module.exports (fromGlobal.js?7c2e:22), <anonymous>:1:1)
    at module.exports (fromGlobal.js?7c2e:13)
    at Object.eval [as getConstructor] (fromRequireContextWithGlobalFallback.js?7c97:17)
    at Object.mountComponents (index.js?0542:82)
    at HTMLDocument.ReactRailsUJS.handleMount (index.js?0542:124)
    at HTMLDocument.dispatch (event.js?268c:338)
    at HTMLDocument.elemData.handle (event.js?268c:146)

index.js?0542:89 [react-rails] Cannot find component: 'PerformanceRow' for element <div data-react-class=​"PerformanceRow" data-react-props=​"{"id":​5}​">​</div>​

index.js?0542:91 Uncaught Error: Cannot find component: 'PerformanceRow'. Make sure your component is available to render.
    at Object.mountComponents (index.js?0542:91)
    at HTMLDocument.ReactRailsUJS.handleMount (index.js?0542:124)
    at HTMLDocument.dispatch (event.js?268c:338)
    at HTMLDocument.elemData.handle (event.js?268c:146)

I've tried the following:

  1. Rolling back uglifier to 3.0
  2. Moving the ReactRailsUJS both above and below import "turbolinks" and javascript_include_tag "application.js" in my main views/layouts/application.html.haml, and removing/adding ReactRailsUJS.detectEvents() in app/javascript/packs/application.js.
  3. Adding console.log(componentRequireContext.keys()) above the ReactRailsUJS initializer, which prints ["./PerformanceRow.jsx"] in my Chrome console.

Here's my directory structure:

app/javascript/packs/
├── application.js
├── hello_react.jsx
└── server_rendering.js

app/javascript/components/
└── PerformanceRow.jsx

And some code:

// app/javascript/packs/application.js

import ReactRailsUJS from 'react_ujs'

// Support component names relative to this directory:
var componentRequireContext = require.context("components", true, /\.jsx?$/)
// => prints ["./PerformanceRow.jsx"] in Chrome console
console.log(componentRequireContext.keys());
ReactRailsUJS.useContext(componentRequireContext)
// app/javascript/components/PerformanceRow.jsx

import React, { Component } from "react"
import ReactDOM from "react-dom"

class PerformanceRow extends Component {
  render() {
    return (
      <tr className="performance">
        <td>HEY THERE</td>
        <td />
        <td />
        <td>Delete</td>
      </tr>
    )
  }
}

export default PerformanceRow;
-# Extract from app/views/performances/_embedded_form.html.haml
    = react_component 'PerformanceRow'

As far as I can tell, I've double-checked capitalization and name-mangling, and componentRequireContext can definitely find the correct file. However, PerformanceRow still seems to be unreachable. I can also access ReactRailsUJS in the global context of the Chrome console.

I should also mention that the default hello_react.jsx works just fine, but that the problem here seems to lie in ReactRailsUJS and the use of the components/ directory. Tried to trim things as best I could, but let me know if I need to include any other information.

andrewcsmith avatar Jul 31 '17 17:07 andrewcsmith

Hi! Thanks for the great bug report.

What if you add .jsx to your react_component call, for example:

react_component 'PerformanceRow.jsx'

?

I think webpack does some magic with the .js file extension, but I bet that it works differently with .jsx, does that work any better?

rmosolgo avatar Jul 31 '17 17:07 rmosolgo

Thanks for the quick reply!

That does not work. It changes the last error to "Cannot find component: 'PerformanceRow.jsx'" but otherwise it's the same. I also tried running PerformanceRow.jsx through babeljs.io and saving it to app/javascript/components/PerformanceRow.js but that didn't solve it either.

andrewcsmith avatar Jul 31 '17 18:07 andrewcsmith

Can you try accessing the componentRequireContext directly to make sure it's working?

Add to packs/application.js

// Attach it to the window: 
window.componentRequireContext = componentRequireContext

Then in Chrome console, access the globally-available componentRequireContext and try to require the module:

window.componentRequireContext 
// => should return the require context 
window.componentRequireContext.require("./PerformanceRow")
// does that return the react component? 

If that doesn't return the react component, try a few variations, just in case a file extension might help.

If that does return the component, then there must be some error in react-rails code which is messing up that lookup!

rmosolgo avatar Jul 31 '17 18:07 rmosolgo

Yep – here's my Chrome console:

window.componentRequireContext("./PerformanceRow.jsx").default
// -> function PerformanceRow() {
//        _classCallCheck(this, PerformanceRow);
//        [... more babel-compiled code...]

andrewcsmith avatar Jul 31 '17 19:07 andrewcsmith

Oh, however, I do have to add the extension to get window.componentRequireContext to work.

The original error message from the helper is Cannot find module './PerformanceRow'., and when I try window.ComponentRequireContext("./PerformanceRow") I get the exact same error message.

This leads me to believe that, somewhere, react-rails is translating react_component 'PerformanceRow' into a call to the module without any extension. When I try react_component 'PerformanceRow.jsx', it thinks that .jsx is a member of module PerformanceRow and still doesn't correct the filename. (That's just my assumption - might be something else of course.)

andrewcsmith avatar Jul 31 '17 19:07 andrewcsmith

it thinks that .jsx is a member of module PerformanceRow

you're so right about that!

Here's that lookup code:

https://github.com/reactjs/react-rails/blob/master/react_ujs/src/getConstructor/fromRequireContext.js#L9

I think we could just treat .jsx as a special case, that way .jsx would Just Work. What do you think?

rmosolgo avatar Jul 31 '17 19:07 rmosolgo

I think requiring react_component "MyComponent.jsx" instead of just react_component "MyComponent" could run into confusion (thinking about people copy-pasting from tutorials, stackoverflow, etc), and probably will breed lots of little spammy issues. Is there a way to make react_component "MyComponent" find the .jsx file, so that (most) tutorial code would still work as-written?

andrewcsmith avatar Jul 31 '17 19:07 andrewcsmith

I'm not sure how to tell if the .jsx is needed ahead of time, so the best i can think of is

try {
  // Load by name 
} catch (err) { 
  // Load by name+.jsx 
}

?

rmosolgo avatar Jul 31 '17 19:07 rmosolgo

Yep, that makes sense to me. In the call to reqctx, right?

andrewcsmith avatar Jul 31 '17 19:07 andrewcsmith

yep, that's it! if it throws an error, i guess we just try again with a .jsx.

it's too bad, thinking how that will add some latency to every single page load.

I can think of two ways to address that:

  • Keep an in-memory cache of { whatWasInTheDom => whatWeUsedWithWebpack } so that we can keep track of whether the .jsx was needed or not.
  • Support .jsx in react_component so that perf-conscious users can hardcode it

Or, leave it for later , which is also fine. apparently not too many folks have come across this bug 😬

rmosolgo avatar Jul 31 '17 19:07 rmosolgo

Excellent! I've opened a PR #759 to fix this. This fix solves the issue on my local machine.

I agree about the performance thing though. I'm not particularly savvy with js performance optimization, or with the react/webpack pipeline, so I'm not really the one to do that. But thanks for your help and I'm happy to test future things with this same app if needed.

andrewcsmith avatar Jul 31 '17 19:07 andrewcsmith

If you have this issue, and the above did not solve it, it may be because you previously installed react-rails to work with the rails asset pipeline, and then installed webpacker later.

Removing //= require react and //= require react_ujs from application.js or application.js.coffee fixed this for me.

I'm assuming that having react and especially react_ujs in the asset pipeline meant that ReactRailsUJS was being set up to expect components also from the asset pipeline, and that setup was taking precedence over the webpacker setup.

chrisjingram avatar Aug 24 '17 14:08 chrisjingram

I installed both at the same time, and didn't have either in the asset pipeline. In the original problem, react-rails was finding components with .js extensions, just not .jsx extensions. Wonder why that would do it.

andrewcsmith avatar Aug 24 '17 16:08 andrewcsmith

I'm facing the exact same issue at the moment. Had .js files so I changed them to .jsx but having the same issue still. Everything is almost the same as the setup @andrewcsmith had, but I can't fix it by changing the extension. Any ideas, anyone?

It's worth noting that I'm moving from the assets pipeline setup to Webpacker. I moved my components folder from assets/javascript/ to /javascript and removed react and react_ujs from application.js.

obedparla avatar Aug 28 '19 15:08 obedparla

Update: I was wrong about the above, it does work with .jsx but I was importing the files directly with their name without any folder just as it's done with the asset pipeline, but it seems that with Webpacker (or at least my setup) you need to specify the folder they're on too:

react_component('myfolder/mysubfolder/Component' .....)

This seems a bit odd since I didn't read anything about adding folders in the react-rails documentation 🤔

obedparla avatar Aug 29 '19 07:08 obedparla

Having almost the exact same issue. After following the fixes above, I've hit a dead end. Running console.log(componentRequireContext.keys()); does correctly show the component ["./post.jsx"] However, running window.componentRequireContext.require("./Post.jsx") from the Chrome console throws this error:

*$:10 Uncaught Error: Cannot find module 'Post.jsx'.
    at webpackContextResolve (.*$:10)
    at webpackContext (.*$:5)
    at <anonymous>:1:8

No combination of the component name, file extension, or folder path fixes this issue for me. Any ideas for a way forward?

nick-bigger avatar Oct 10 '19 19:10 nick-bigger

@nbigger can you make an example rails app that displays the problem? I closed PR #759 because nobody involved was able to produce a replication. If you have one then we can finally get this fixed.

BookOfGreg avatar Oct 11 '19 15:10 BookOfGreg

@BookOfGreg I run into this problem when I apply filtering to require.context() to only load production JS/JSX/TS/TSX files in my application pack. The main purpose of me doing this is to have my test files/folders excluded from asset compilation, since they shouldn't be shipped to production. My plight with passing the appropriate filter regex to require.context is documented here: https://github.com/rails/webpacker/issues/1818#issuecomment-576428973

Unfortunately, I then run into a problem with react-rails, in that my calls to react_component started failing. I have a hunch it is due to directory names no longer being loaded by require.context, but I'm not too sure. Anyway, I hope that this provides you with another angle to think about this problem.

I am happy to delve further into reproduction steps with you.

ecbrodie avatar Jan 20 '20 21:01 ecbrodie

@ecbrodie Did you end up solving that? I am in the same situation.

akshaysmurthy avatar Jan 24 '20 12:01 akshaysmurthy

I was forced to adjust my regex to allow directories to be included, as well as the JavaScript/Typescript files themselves:

const componentRequireContext = require.context("components", true, /^(?!.*__tests__\/.*$).+$/)

I am not fond of this solution though, I feel like react-rails should be able to load components by name if the regex excludes directories but loads files.

ecbrodie avatar Jan 24 '20 21:01 ecbrodie

@ecbrodie That's true, thanks for the update.

akshaysmurthy avatar Jan 27 '20 03:01 akshaysmurthy

@ecbrodie This just worked but I don't understand why it did. I experimented for some hours without any success before trying your solution. Are you excluding the ./ from the component inclusion ?

chrisvel avatar May 18 '21 08:05 chrisvel

I switched jobs last year and no longer work on the project in question, nor work on a project using neither webpacker nor react-rails. Thus I am unable to comment further to this issue.

ecbrodie avatar May 18 '21 12:05 ecbrodie