Standalone React components with Rails6 / Webpacker - ruby-on-rails

I'd like to know how to / [accepted way] to add standalone react components to specific pages in rails, using webpacker. The use case is that I have a Rails6 app that I intend to use Rails functionalities for the view, instead of a full React SPA. I would, however, like to use specific custom react components in specific rails views. The app has webpacker installed and configured for react.
Questions I have:
is react-rails the right gem / pattern for this usage? I have tried using it and it does perfectly fill the use case, but I was wondering if it can be done without adding this gem since it since it seems like webpacker itself has react support.
Is it required that the webpacker <%= javascript_pack_tag => should always go in an application layout (head tag), and dynamically render a pack to the end of the body(appendChild), like the examples say? I'd rather include the pack tag in a view, and have it render directly to a div I've specified. (code for this below)
How does one send react props to a standalone react component that is rendered by the <%= javascript_pack_tag => ? Would the component have to be written to fetch any data it needs on mount?
Things I've tried:
I looked through the documentation for webpacker, and as many of the issues I could find using the keywords "standalone" and "react" and couldn't find anything.
Sample project below: it works.. but I'm not sure if it's the right way to do this and I'm not sure how to address question 3 from above (regarding passing props)
Initialize
rails new petstore --webpack=react
bin/rails generate scaffold Pet pet_type:string pet_name:string
bin/rails db:migrate
package.json
{
"name": "petstore",
"private": true,
"dependencies": {
"#babel/preset-react": "^7.12.10",
"#rails/actioncable": "^6.0.0",
"#rails/activestorage": "^6.0.0",
"#rails/ujs": "^6.0.0",
"#rails/webpacker": "5.2.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"turbolinks": "^5.2.0"
},
"version": "0.1.0",
"devDependencies": {
"webpack-dev-server": "^3.11.2"
}
}
config/webpacker.yml
default: &default
source_path: app/javascript
source_entry_path: packs
public_root_path: public
public_output_path: packs
cache_path: tmp/cache/webpacker
webpack_compile_output: true
# Additional paths webpack should lookup modules
# ['app/assets', 'engine/foo/app/assets']
additional_paths: []
# Reload manifest.json on all requests so we reload latest compiled packs
cache_manifest: false
# Extract and emit a css file
extract_css: false
static_assets_extensions:
- .jpg
- .jpeg
- .png
- .gif
- .tiff
- .ico
- .svg
- .eot
- .otf
- .ttf
- .woff
- .woff2
extensions:
- .jsx
- .mjs
- .js
- .sass
- .scss
- .css
- .module.sass
- .module.scss
- .module.css
- .png
- .svg
- .gif
- .jpeg
- .jpg
development:
<<: *default
compile: true
# Reference: https://webpack.js.org/configuration/dev-server/
dev_server:
https: false
host: localhost
port: 3035
public: localhost:3035
hmr: false
# Inline should be set to true if using HMR
inline: true
overlay: true
compress: true
disable_host_check: true
use_local_ip: false
quiet: false
pretty: false
headers:
'Access-Control-Allow-Origin': '*'
watch_options:
ignored: '**/node_modules/**'
test:
<<: *default
compile: true
# Compile test packs to a separate directory
public_output_path: packs-test
production:
<<: *default
# Production depends on precompilation of packs prior to booting for performance.
compile: false
# Extract and emit a css file
extract_css: true
# Cache manifest.json for performance
cache_manifest: true
app/javascript/components/PetAnimation.js
import React from "react";
import PropTypes from "prop-types"
class PetAnimation extends React.Component {
constructor(props) {
super(props);
this.state = {
petType: props.petType,
};
}
render() {
return (
<div>Cute Pet Animation based on Pet Type and other imported libraries</div>
)
}
}
PetAnimation.propTypes = {
petType: PropTypes.string
};
export default PetAnimation;
app/javascript/packs/pet_animation.js
import React from 'react'
import ReactDOM from 'react-dom'
import PetAnimation from "../components/PetAnimation";
document.addEventListener('DOMContentLoaded', () => {
const rootElement = document.getElementById("pet-animation");
if (rootElement != null) {
ReactDOM.render(<PetAnimation/>, rootElement)
}
})
app/views/pets/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Pet type:</strong>
<%= #pet.pet_type %>
</p>
<p>
<strong>Pet name:</strong>
<%= #pet.pet_name %>
</p>
<p>
<%= javascript_pack_tag 'pet_animation', 'data-turbolinks-track': 'reload' %>
<div id="pet-animation"></div>
</p>
<%= link_to 'Edit', edit_pet_path(#pet) %> |
<%= link_to 'Back', pets_path %>
... and it works, sort of:

Related

Stimulus JS controllers not connecting. Rails 7 // importmap // stimulus

I recently got rid of webpacker and installed importmap and turbo. My Stimulus JS controllers are not rendering anything.
importmap.rb :
# Pin npm packages by running ./bin/importmap
pin "application", preload: true
pin "#hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "#hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "#hotwired/turbo-rails", to: "turbo.js", preload: true
typewriter_controller.js :
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
connect() {
// some code...
}
}
application.html.erb:
<head>
some stuff..
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag "turbo", type: "module-shim" %>
<%= javascript_importmap_tags %>
</head>
I tried to re-install the stimulus gem but it didn't work. I'm running out of ideas on this one.
As from the stimulus-rails installation guide, I would check the following files are configured this way:
In app/javascript/controllers/index.js
import { application } from "controllers/application"
// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "#hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
In app/javascript/controllers/application.js
import { Application } from "#hotwired/stimulus"
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
export { application }
In app/javascript/application.js
import "controllers"
This assumes you are indeed using importmap and not some other JavaScript bundler (e.g., yarn).

Error when trying to import npm package (easyMDE) in application.js - (Uncaught ReferenceError: EasyMDE is not defined)

Im trying to include easyMDE in my rails 6 project. I was including it from an external source before and that worked (via unpkg.com). To improve loading times I wanted to include it into my project via yarn.
I did the following steps:
yarn add easymde --save
In application.js:
require("easymde");
But when reloading the page I get the following error:
Uncaught ReferenceError: EasyMDE is not defined
When I run webpack-dev-server I see that easymde.js gets compiled:
Built at: 08/11/2020 3:16:09 PM
Asset Size Chunks Chunk Names
js/application-caadc00338c3578fee39.js 1.2 MiB application [emitted] [immutable] application
js/application-caadc00338c3578fee39.js.map 1.39 MiB application [emitted] [dev] application
js/easymde-1f6737d8a160c1180536.js 872 KiB easymde [immutable] easymde
js/easymde-1f6737d8a160c1180536.js.map 805 KiB easymde [dev] easymde
manifest.json 675 bytes [emitted]
ℹ 「wdm」: Compiled successfully.
application.js:
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
require("easymde")
package.json:
{
"name": "logbook_v2",
"private": true,
"dependencies": {
"#rails/actioncable": "^6.0.0",
"#rails/activestorage": "^6.0.0",
"#rails/ujs": "^6.0.0",
"#rails/webpacker": "4.2.2",
"easymde": "^2.11.0",
"turbolinks": "^5.2.0"
},
"version": "0.1.0",
"devDependencies": {
"webpack-dev-server": "^3.11.0"
}
}
Let me know if you need any more code. :-)
Update
Here is the file where I use the editor:
<%= render "components/sidebar" %>
<div class="form-wrapper post-form-wrapper">
<%= form_with model: #post, class: 'form post-form' do |f| %>
<%= f.text_field :name, placeholder: 'title', class: 'field post-field' %>
<%= f.text_area :content, id: 'mde' %>
<%= f.submit 'create post', class: 'form-button post-form-button' %>
<%end%>
</div>
<script type="text/javascript" charset="utf-8">
var easyMDE = new EasyMDE({
element: document.getElementById("mde"),
placeholder: "Write your post here...",
autosave: {
enabled: true,
uniqueId: <%= current_user.id %>
},
blockStyles: {
code: "~~~"
},
minHeight: "calc(100vh - 320px)",
renderingConfig: {
codeSyntaxHighlighting: true
},
promptURLs: true,
toolbar: ["bold", "italic", "strikethrough", "heading", "|", "code", "quote", "clean-block", "|", "link", "image", "|", "unordered-list", "ordered-list", "|", "preview", "side-by-side", "fullscreen", "|", "guide"]
});
</script>
If I put in import * as EasyMDE from 'easymde'; (suggested by #Ninh Le) at the beginning of the script I get this error: Uncaught SyntaxError: import declarations may only appear at top level of a module
Easymde using module.exports, you can find it here
So to use it in with yarn and rails 6 I think the way is in js file:
import * as EasyMDE from 'easymde';
var easyMDE = new EasyMDE({element: document.getElementById('my-text-area')});
Edit: To make it can call globally, call this in application.js file
import * as EasyMDE from 'easymde';
window.EasyMDE = EasyMDE;

How to use Rails 5.2 credentials in another .yml file?

I'm using the Cloudinary gem to allow a user to upload their avatar. In the docs, they offer a few ways to supply your api_key and api_secret, however I'm unable to get it work correctly with Rails credentials.
I've tried with the cloudinary.yml:
---
development:
cloud_name: test_cloud
api_key: <%= Rails.application.credentials.cloudinary[:api_key] %>
api_secret: <%= Rails.application.credentials.cloudinary[:api_secret] %>
enhance_image_tag: true
static_file_support: false
production:
cloud_name: test_cloud
api_key: <%= Rails.application.credentials.cloudinary[:api_key] %>
api_secret: <%= Rails.application.credentials.cloudinary[:api_secret] %>
enhance_image_tag: true
static_file_support: true
test:
cloud_name: test_cloud
api_key: <%= Rails.application.credentials.cloudinary[:api_key] %>
api_secret: <%= Rails.application.credentials.cloudinary[:api_secret] %>
enhance_image_tag: true
static_file_support: false
And I've also tried using a cloudinary.rb initializer:
Cloudinary.config do |config|
config.cloud_name = "test_cloud"
config.api_key = Rails.application.credentials.cloudinary[:api_key]
config.api_secret = Rails.application.credentials.cloudinary[:api_secret]
config.enhance_image_tag = true
config.static_file_support = false
end
When I attempt to upload the image, I receive a 500 error CloudinaryException (Must supply api_key). I know that the actual upload itself works because I hard-coded the values into the cloudinary.yml file to test it.
I've also tried using the .fetch function mentioned below, however I receive a key not found: :api_secret (KeyError)
How can I use Rails credentials within either of these files?

Bootstrap JS functions not loading in Rails 6/Webpacker

Climbing the learning curve with Webpacker and Rails 6.
I've installed Boostrap 4 using Yarn and Webpacker. When I try play with Bootstrap components in a browser's JS console then I get: TypeError: $.fn.button is undefined.
If I remove the Yarn installation add a Bootstrap CDN link in the head of the HTML (the old way) everything works fine. When I remove the CDN links and revert to using Webpacker I get back to the error above.
Here is my Webpacker setup:
// package.json
{
"name": "depot",
"private": true,
"dependencies": {
"#rails/actioncable": "^6.0.0-alpha",
"#rails/activestorage": "^6.0.0-alpha",
"#rails/ujs": "^6.0.0-alpha",
"#rails/webpacker": "^4.0.7",
"bootstrap": "4.2.1",
"jquery": "^3.4.1",
"popper.js": "^1.15.0",
"turbolinks": "^5.2.0"
},
"version": "0.1.0",
"devDependencies": {
"webpack-dev-server": "^3.8.0"
}
}
// enviornment.js
const { environment } = require("#rails/webpacker");
const webpack = require("webpack");
environment.plugins.append(
"Provide",
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery',
Popper: ["popper.js", "default"]
})
);
module.exports = environment;
// application.js
require("#rails/ujs").start();
require("turbolinks").start();
require("#rails/activestorage").start();
require("channels");
require("jquery");
require("bootstrap");
import "./src/application.scss";
# layouts/application.html.haml
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}
%title Depot
= csrf_meta_tags
= csp_meta_tag
= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
Is there something I should be doing to export the Bootstrap functions to the global namespace?
Found a forum where this was discussed: https://gorails.com/forum/how-to-use-bootstrap-with-webpack-rails-discussion
They mentioned it could be caused by having jQuery loading multiple times, with the latest declaration preventing Bootstrap's JS from loading. I couldn't find any evidence of this in my project.
For me the fix ended up being a few lines added to the webpack entry point that exposed jQuery to the browser:
# packs/application.js
import JQuery from 'jquery';
window.$ = window.JQuery = JQuery;
I noticed even I removed require 'jquery' from applications.js, jquery still appears in the compiled application.js, so I removed this from enviroment.js
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery',
and add this to application.js
import JQuery from 'jquery';
window.$ = window.JQuery = JQuery;
restart rails server, it works!
If you want to know why see this answer

SyntaxError occurs when reading js.erb file with webpacker

I'm currently developing with Rails5 and React.
And to use React, we use a gem called react-rails, and this gem uses webpacker.
At that time, in React, js.erb file was created to refer to the image precompiled with assets.
The information I looked up on the net had rails-erb-loader, so it should have been readable.
However, SyntaxError occurred.
I will write the details below.
ErrorLog
# ./bin/webpack-dev-server
~~~
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /home/myproject/app/javascript/components/sample.js.erb: Unexpected token (1:1)
> 1 | <% helpers = ActionController::Base.helpers %>
sample.js.erb
<% helpers = ActionController::Base.helpers %>
export const png_path = "<%= helpers.image_path('sample.png') %>"
package.json
{
"name": "Sample",
"private": true,
"dependencies": {
"#babel/preset-react": "^7.0.0",
"#rails/webpacker": "^4.0.7",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"lodash": "^4.17.14",
"prop-types": "^15.7.2",
"rails-erb-loader": "^5.5.2",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"react_ujs": "^2.6.0",
"reactjs-popup": "^1.4.1"
},
"devDependencies": {
"webpack-dev-server": "^3.8.2"
}
}
How can I solve this error?
We will wait for the answer.
This issue was not resolved. Sorry

Resources