Rails - React components doesn't get mounted on second page load - ruby-on-rails

I'm using Rails with webpacker with react.
I'm loading my components like this in my view file:
<div id="mycomponent"></div>
<%= javascript_pack_tag 'components/mycomponent' %>
In mycomponent, I have:
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<MyComponent />,
document.getElementById('mycomponent'),
)
})
So on initial page load, mycomponent is mounted.
But when I visit another page, hit back or click on a link to the dashboard (in this case), the component doesn't load. Had to hit refresh to load it.
I think it has something to do with some caching techinques that Rails is using?
How do I get the components to load on every request

So turbolinks was doing some optimizations behind.
Realized that the url in chrome inspector remained the same when I changed pages.
Pages were loading fully after removing //= require turbolinks from app/assets/javascript/application.js.
Seen some people pointing to jquery.turbolinks for onload issues with turbolink. Not tested though.

Related

How to integrate JqTree in Rails 6 with webpacker, tree is not a function

I am really wanting to know how to integrate jqTree as a webpacker webpack in my Rails 6 application
UPDATE:-
Using yarn add jqtree seems to have magically cleared up most of my issues however I am currently facing tree is not a function error
I am using the Ancestry gem to organise menu items and I need a drag and drop javascript tree view solution that will work nicely with the Ancestry gem. I have picked on jqTree as my desired solutions but I am happy to be persuaded to use an alternative as there seem to be a lot around but initially I would just like to be able to at least get a tree view working within Rails 6
Presumably I have to start by setting up jQuery, for which there are plenty of resources on how to do this so I guess this is more about how to get any jQuery component up and running in a Rails 6 app
I guess I'll have to start with a jsx file and import some stuff and import some css into application.scss but just what this should look like I really am unsure of
So far I have setup jQuery according to the instructions here https://www.botreetechnologies.com/blog/introducing-jquery-in-rails-6-using-webpacker
I can confirm with a simple alert that this is all hooked up and working
I have made some more progress
Instead of downloading the jqTree files, I have used yarn to install jqTree
replacing
I have downloaded the jqTree files and unpacked them to folder called jqTree inside my javascript/packs folder
with
yarn add jqtree
and now I have sorted out the require which is as it should be without the ;
So
require ;'jqTree/tree.jquery.js'
becomes
require('jqtree')
also in my javascript/packs folder I have created a sortable.js file which did contain the following
require ;'jqTree/tree.jquery.js'
jQuery(window).on('load', function () {
alert("Done"); //This works!
});
$(function() {
$('#tree1').tree({
data: data,
autoOpen: true,
dragAndDrop: true
});
})
the ; in the require statement confuses me a lot but the console error was demanding it
This now looks like this
require("jqtree");
$(function() {
alert($('#tree1').data('items'));
});
$('#tree1').tree({
data: $('#tree1').data('items'),
autoOpen: true,
dragAndDrop: true
});
With the above code I get an reference error: data is not defined
In a view I have the following code
<%=javascript_pack_tag("sortable")%>
<%= content_tag "div", id: "tree1", data: {items: #menu_items} do %>
Loading items...
<% end %>
The issue I have now is that my browser is reporting an error that tree is not a function.
In my application.css.scss I have
*= require "jqtree.css"
Which doesn't work
I have a detailed answer for this that has taken me quite sometime to put together, I will update this answer with that detail over the coming days but it starts with getting everything hooked up properly with jQuery, jQuery-ui and the componment itself which in this case is jqTree
Yarn is definitely the answer
Starting with the command line to add the relevant packages
yarn add jquery
yarn add jquery-ui
yarn add jqtree
Once the relevant yarn packages are installed I needed to make jQuery and jQuery-ui available for reasons that I am yet to fully comprehend, a simple require is not enough
Following this post https://www.botreetechnologies.com/blog/introducing-jquery-in-rails-6-using-webpacker
I setup my environment.js file in the config/webpacker folder to look like this
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
);
const aliasConfig = {
'jquery': 'jquery-ui-dist/external/jquery/jquery.js',
'jquery-ui': 'jquery-ui-dist/jquery-ui.js'
};
environment.config.set('resolve.alias', aliasConfig);
module.exports = environment
After restarting my server I created a simple js file called test.js in the app/javascript/packs folder which as far as I can tell is where all javascript now resides although there is still the possibility of using sprockets by putting javascript in the assets/javascript folder, I have been advised, with no explanation as to why, that it is not a good idea to mix webpacker with sprockets for serving javascript.
Webpacker does has the ability to serve stylesheets as well, however I still prefer my stylesheets to be served by sprockets in the app/assets folder and using stylesheet link tags as traditionally done in Rails apps as testing proved to be more efficient this way so mixing sprockets with webpacker in this way doesn't seem to be an issue.
I just chucked a simple alert message into the test.js file just to check that webpacker and jQuery was all hooked up properly.
So test.js looks like this
require("jquery-ui");
require("jqtree");
$(function() {
alert($('#tree1').data('items'));
});
Then to use the javascript I just included a javascript pack tag in the view I wanted to use it in like so.
in a random edit.html.erb view
<h1>Editing Menu Item</h1>
<%= render 'form', menu_item: #menu_item %>
<%= link_to 'Show', [:admin, #menu_item] %> |
<%= link_to 'Back', admin_menu_items_path %>
<%=javascript_pack_tag("test")%>
No need for paths or extensions to the file name, it just works!
I find this approach to be really neat for two reasons
1) I am not unnecessarily bloating out web pages by including page specific javascript in every single page by including it in the application.js file which gets included by default in the layout and therefore in every page.
2) I don't seem to have to worry about DOM loaded events and so far no issues with turbolinks interfering with it, although I suspect that may be more by luck than judgement and I may have to revise that thought in the future and make use of 'data-turbolinks-track': 'reload' option, but so far so good and this has not been necessary.
So now that all is hooked up and working it was time to make the jqTree component work with ancestry gem to be able to structure and organise the menu items for the site,
But up to this point it has been a simple matter of using yarn top install components and hooking up jQuery properly. I didn't use yarn initially and that led me to all sorts of problems resulting in my original question.
The rest is to follow...

Capybara prevents attaching onsubmit event to form

In my rails app I attach onsubmit event to my form using jquery like this:
$("form.my_form").submit(function(){
alert("test")
return true;
});
This is working fine when I manually open the page and submit the form (clicking on the submit button) - I see the alert "test"
BUT I have a Capybara test (over Selenium) that loads the page, fills in some values in the form, and then clicks on submit.
And the function is NOT triggered.
Note 1: when running the test the form is indeed submitted, new values recorded in the DB etc. so there is no problem with clicking on the submit button itself
Note 2: if instead of jquery, in the rails view in the form_for tag I put
html: {onsubmit: "onsubmitfunc();"}
with the alert in the onsubmitfunc() - everything works as expected in the capybara test
Note 3: in order for the jquery way to work the js file is defer loaded e.g.:
<%= javascript_include_tag 'application', defer: true %>
I am very new to JQuery, so am I doing something criminal there?
Or Capybara has some problem? with defer loading of the JS file? Or with JQuery? Any ideas?
Since you're using Selenium - the most common reason would be that you have an error in one of your JS assets. When in the dev environment all your JS assets are served in separate files which means an error in one file doesn't stop others from being processed. In the test environment (and production) the assets are concatenated into one file which means an error in any file can cause assets concatenated after it to not be processed.
Check the browsers console log for any JS errors and fix them.

<select> design is not visible at first download

I use Rails 4 and bootstrap-select-rails gem.
When I download page with my form, I got this
When I refresh page, I got this
If I turn off turbolinks, design is ok, but site is very slow.
How to display the correct design at first download without disabling turbolinks?
UPD:
at application.js:
//= require bootstrap-select
at application.scss:
#import "bootstrap-select";
at view:
=select_tag "document_id",
options_from_collection_for_select(#documents,'id', 'name'),
class: "selectpicker",
"data-live-search".to_sym => "true"
I added data-no-turbolink attribute to link
New

Rails plain href with remote: true being treated as JS on one place in page but HTML in another

My problem
I realize that there are a ton of questions about using remote: true and having the server process the request as HTML instead of javascript so that you get a MissingTemplate Error on SO already.
So far none of them answer my question. To prove that:
I know that I have included / bundled etc jquery, jquery-ujs etc because this link used to work. Also I can see them getting included on the rendered page and also as mentioned in the question, I'm getting the server to successfully process the request as JS in many places.
I know that I'm not having a precompiled assets issue because I don't have any precompiled assets (deleted the public/assets folder myself just to make sure).
If I put the following on my index page right at the top
<%= link_to "Test", "/create_tags_dialog", :remote => true %>
It renders the html perfectly:
<a data-remote="true" href="/create_tags_dialog">Test</a>
and correctly calls the remote script, no TemplateMissing Error because the request is processed as JS.
Just to be sure I even copied the rendered HTML back onto the index and they both work as expected:
<a data-remote="true" href="/create_tags_dialog">Test2</a>
(i.e. the server processes the request as JS and I get no TemplateMissing Error)
Now for the problem
If I put the same link_to line into a bootstrap dropdown <ul> the request gets processed as HTML and I get a MissingTemplate Error. I checked and the rendered html is identical! Again, just to check, I put the raw html in as well <a data-remote="true" href="/create_tags_dialog">Test</a>
The only difference between identical uses of this anchor tag is that the second use (the broken one) is inside of a bootstrap dropdown which gets rendered as a partial on page load.
Could it be the partial? Please let me know if I can include any outputs, error logs, screenshots etc to help clarify things.
Note
I see a few solutions like this one
Which recommend ensuring this line is in the index
<%= javascript_include_tag "jquery", "jquery_ujs" %>
I don't have that line exactly, mine is
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
and my application.js is
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= require bootstrap.min
//= require turbolinks
//= require jquery-readyselector
//= require fastclick
//= require_tree .
Also the link is working in a different div of the same page at the same time, but just in case I thought I'd mention this. . .
Update
Thanks to AmShaegar I discovered the change in my code that causes this to break. I added a stopPropagation() to my dropdowns because I want them to stay open after click like this:
$('#tags-filter-dropdown').click (e) ->
e.stopPropagation()
If I comment this out, my remote-link goes back to working. Any suggestions for how I can both keep that dropdown from closing and activate a remote link? I tried implementing AmShaegar's solution of giving the link a class and preventing propagation / default on the link specifically but it just killed the link (unresponsive). Is there a middle ground?
Based on this question and my comment above:
It may help if you disable bootstraps onclick handler for your remote link. You need to give your link a special class and then write your own onclick handler.
link_to "Test", "/create_tags_dialog", :remote => true, { class: 'no_bootstrap' }
$(".no_bootstrap").click(function(e) {
e.stopPropagation();
e.preventDefault();
// You may still want to close the drop down manually here.
// See bootstrap documentation for this.
});​
Update
Looks like you need to trigger the remote click event manually after you stopped propagation. Therefor you need to know how rails does it internally.
From viewing the source code I would say this could work like this:
$(".no_bootstrap").click(function(e) {
e.stopPropagation();
e.preventDefault();
$(e.target).trigger('click.rails');
});​

Rails 4 + Turbolinks: JS in the head or at the bottom of the body?

I'm used to sticking my JS at the bottom of my body element (as per HTML5Boilerplate), but since TurboLinks, which will be on by default in Rails 4, reloads the body (and title) on every request, would it make sense to put my JS in the head from now on? Are there any decent guides or best practices on this yet?
If you're using Turbolinks 5, you can now put turbolinks at the bottom of your body (and follow web dev best practices for rendering a page). This also helps with SEO ever so slightly. Simply add your scripts to the bottom of your body and add
data-turbolinks-eval="false"
to your script tag. This will prevent turbolinks from evaluating after the initial page load. Here's how it is done with the javascript_include_tag:
<%= javascript_include_tag 'application', 'data-turbolinks-eval' => 'false'%>
Then just use
$(document).on('turbolinks:load', function() {
// code to be executed when use changes pages
});
Also precompiled application.js (as per the assets pipeline) is on and in head by default. The reasoning is that even if the js is loaded before the rest of the page, as the js is always the same it will be loaded from cache from the second request onwards, so there is no actual reason to serve it at the bottom of the body anymore.

Resources