I having an issue with the Redux store in getting react_on_rails where I want to switch the page and it doesn't work correctly for me. I am losing the redux store if I change the page or reload it.
Also, I am sorry for a pile of code I put, but I really don't know anything else or ways to fix it, the official docs for react_on_rails and redux did not help me and it more likely confuse me how it should work. if anyone has a clue how to make it work, in advance, thank you
rootStore.js
import { combineReducers, applyMiddleware, createStore } from 'redux';
import middleware from 'redux-thunk';
import reducers from '../reducers';
export default (props, railsContext) => {
// eslint-disable-next-line no-param-reassign
delete props.prerender;
const combinedReducer = combineReducers(reducers);
const newProps = { ...props, railsContext };
return applyMiddleware(middleware)(createStore)(combinedReducer, newProps);
};
reducers/index.js
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import cartReducer from './cartReducer';
const rootReducer = combineReducers({
routing: routerReducer,
cart: cartReducer,
});
export default rootReducer;
the component that I am trying to do magic with
// #flow
import * as React from 'react';
import { connect } from 'react-redux';
import { addToCart} from '../actions/setCart';
import styles from './AddToCart.module.scss';
type Props = {
productId: number,
}
type State = {}
class AddToCart extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
}
onClick = () => {
console.log(this.props.productId);
this.props.addToCart(this.props.productId);
};
render() {
console.log('this.props.cart', this.props.cart);
return <button
onClick={this.onClick}
className={styles.button}
>Add To Cart</button>;
}
}
const AddToCartRedux = connect(
state => ({
cart: state.cart,
}),
dispatch => ({
addToCart: id => {
dispatch(addToCart(id));
},
}),
)(AddToCart);
export default AddToCartRedux;
my entry point
import ReactOnRails from 'react-on-rails';
import ProductDetailPage from '../pages/ProductDetailPage';
import rootStore from '../store/rootStore';
ReactOnRails.setOptions({
traceTurbolinks: true,
});
ReactOnRails.register({ProductDetailPage});
ReactOnRails.registerStore({rootStore});
my layout application
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= notice %>
<%= alert %>
<%= redux_store('rootStore', props: {}) %>
<%= react_component('ProductDetailPage', props: {product: #product.id}) %>
<%= yield %>
<%= redux_store_hydration_data %>
</body>
</html>
Since the Redux store is initialized when you load a page, navigating to a new page or reloading the page will cause all your JS assets to reload and re-initialize, causing your store to reset.
If you want to change the browser's location without a page reload, you would need to use something like react-router to handle navigation between React components instead of going through a full request/response cycle with the Rails server.
Related
I have the following tag in my application.html.erb file: <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
Initially it is in the head section, which is the default for Rails 5.2 I assume. The problem with this was that it wouldn't load the JS files in my assets/javascripts folder. I was advised to move the tag to somewhere that was after the <%= yield %> part but still within the body. This worked fine and the JS from that folder loaded ok. The problem now is that I am using the leaflet-rails gem and the map will not load when the javascript_include_tag is in the body... Is there anything I can do so that both these things work?
My assets/javascripts/locations.js
var currentLocation = document.getElementById('coordinatesStore');
document.querySelector('.add-button').addEventListener('click', () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(({ coords: { latitude, longitude }}) => {
currentLocation.value = latitude + ", " + longitude;
});
} else {
currentLocation.value = "Geolocation is not supported by this browser.";
}
});
The problem here is you're trying to attach event handlers to elements that do not yet exist. Solution is standard: load the code in the head, but postpone running it until page loads.
// wrap your code in a function, so that it can be executed later
function attachLocationClickHandlers() {
Array.prototype.forEach.call(
document.querySelectorAll('.add-button'),
(button, i) => {
// do something on click
}
);
}
document.addEventListener("turbolinks:load", function() {
attachLocationClickHandlers()
})
(or something similar to that. I don't know javascript)
How do you pass props or data to the component when using the
javascript_pack_tag tag?
currently
/application.html.erb
<%= javascript_pack_tag 'flash_messages', data: {messages: flash_messages} %>
<%= javascript_pack_tag 'CropImages' %>
<%= content_tag :div,
id: "appointments_data",
data: #product.to_json,
front_image: url_for(#product.front_image),
back_image: url_for(#product.back_image) do %>
<% end %>
react component :
document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('appointments_data')
const data = JSON.parse(node.getAttribute('data'))
const frontImage = node.getAttribute('front_image')
const backImage = node.getAttribute('back_image')
ReactDOM.render(
<CropImages data={data} front_image={frontImage} back_image={backImage} />,
document.body.appendChild(document.createElement('div')),
)
})
It could be done in several ways.
1.you could by pass props "by hand"
document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('flash_messages_data')
const data = JSON.parse(node.getAttribute('data'))
ReactDOM.render(
<FlashMessages messages={data} />,
document.body.appendChild(document.createElement('div')),
)
})
Or you could you gem, which basically does almost the same:
https://github.com/renchap/webpacker-react
in you view
<%= react_component('FlashMessages', { messages: [] }) %>
<%= javascript_pack_tag 'components/flash_messages' %>
and in your component
import React, { Component } from 'react'
import WebpackerReact from 'webpacker-react'
class FlashMessages extends Component {
...
render() {}
}
WebpackerReact.setup({FlashMessages}) // ES6 shorthand for {Hello: Hello}
last line basically parses data attribute generated by the react_component helper, and passes to your component.
I tried to import Material UI into my React On Rails Project.
Everything is okay, but just only Module RaisedButton can't use,
Every time I try to import RaisedButton, I will get the error message like the picture.
My code is below:
import ReactOnRails from 'react-on-rails';
import PropTypes from 'prop-types';
import React from 'react';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import RaisedButton from 'material-ui/RaisedButton';
const App = () => (
<MuiThemeProvider>
<RaisedButton label="hi"/>
</MuiThemeProvider>
)
ReactOnRails.register({
App,
});
UPDATE
hello_world/index.html.erb:
<h1>Hello World</h1>
<%= react_component("App", props: #hello_world_props, prerender: false) %>
layouts/hello_world.html.erb:
<!DOCTYPE html>
<html>
<head>
<title>ReactOnRailsWithWebpacker</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'webpack-bundle' %>
</head>
<body>
<%= yield %>
</body>
</html>
How can I fix it ? Or It's just the bug in React On Rails Gem?
I think I find the problem,
I see the node_module/material-ui/RaisedButton/RaisedButton.js file
return _react2.default.createElement(
_Paper2.default,
{
className: className,
style: (0, _simpleAssign2.default)(styles.root, style),
zDepth: this.state.zDepth
},
_react2.default.createElement(
_EnhancedButton2.default,
(0, _extends3.default)({}, other, buttonEventHandlers, {
ref: 'container',
disabled: disabled,
style: (0, _simpleAssign2.default)(styles.button, buttonStyle),
focusRippleColor: mergedRippleStyles.color,
touchRippleColor: mergedRippleStyles.color,
focusRippleOpacity: mergedRippleStyles.opacity,
touchRippleOpacity: mergedRippleStyles.opacity
}),
_react2.default.createElement(
'div',
{
ref: 'overlay',
style: prepareStyles((0, _simpleAssign2.default)(styles.overlay, overlayStyle))
},
enhancedButtonChildren
)
)
);
In this code, there is the Obj key ref, I delete the ref, and the error would not appears.
I am trying to render the google map api on my web page but I keep getting this error:
edit:1076 Uncaught ReferenceError: $ is not defined
Here is the process. The call that leads to the rendering of the api map begins in an edit.html.haml file.
edit.html.haml
........html.....
........html.....
........html.....
.col-md-5.col-md-push-1
.form-group
%label.control-label!= t('.city_and_country')
.controls
%p.enabled
%input.form-control#location_scratch{ type: :text, autocomplete: :off, value: #account.location }
%a{ href: 'javascript:EditMap.clearLocation();' }= t('clear')
%p.error#not_found{ style: 'display:none' }
= t('.location_error_message')
#map
#-----THIS IS WHERE IT STARTS-------------
!= map_init('map,' #account.latitude ? 7 : 0)
#-------------------------------------------
= f.hidden_field :location
= f.hidden_field :country_code
= f.hidden_field :latitude
= f.hidden_field :longitude
From map_init in the edit.html.haml file we then travel to the map_helper.rb file
map_helper.rb
module MapHelper
def map_init(id, zoom = 2)
map_script_load + map_js_initialization(id, zoom)
end
private
def map_script_load
key = Rails.application.config.google_maps_api_key
uri = "#{request.ssl? ? 'https' : 'http'}://maps.googleapis.com/maps/api/js?v=3&key=#{key}"
"<script async defer src='#{uri}' type='text/javascript'></script>"
end
def map_js_initialization(id, zoom)
javascript_tag <<-JSCRIPT
$(document).on('page:change', function() {
Map.load('#{id}', 25, 12, 2);
Map.moveTo(25, 12, #{zoom});
});
JSCRIPT
end
and then from there it goes to a Map.js file that calls the load and moveTo methods.
The problem though is in the map_js_initialization method. The JSCRIPT javascript code fails for $(document).on('page:change') Why is it failing for the jQuery object? I saw that an error like this occurs because the google maps api loads before the jquery source code. However, I've also placed an async defer on the maps and still no change in result.
I have require jquery in my application.js file and then in my application.html.haml file I have
!!!5
%html
%head
- page_title = content_for?(:html_title) ? "#{yield(:html_title)}" : t('.openhub')
%title= page_title
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
%meta{ name: 'description', content: page_context[:description] }
%meta{ name: 'keywords', content: page_context[:keywords] }
%meta{ name: 'digits-consumer-key', content: ENV['DIGITS_CONSUMER_KEY'] }
%meta{ name: 'google-site-verification', content: 'jKkWeVQ0tB1bffJYg7xXAtcIM-nrjjVxhP3ohb8UH2A' }
= yield :custom_head
= stylesheet_link_tag 'application', media: 'all'
= csrf_meta_tags
%body{ zoom: 1 }
= yield :session_projects_banner
.container#page
%header= render partial: 'layouts/partials/header'
= render partial: 'layouts/partials/page'
.clear
%footer= render partial: 'layouts/partials/footer'
= yield(:javascript) if content_for?(:javascript)
= javascript_include_tag 'application',
'https://www.google.com/recaptcha/api.js',
'https://cdn.digits.com/1/sdk.js',
'//cdn.optimizely.com/js/3568250046.js',
cache: 'cached_js_files',
async: true
- if Rails.env.production?
<script type="text/javascript">
(function() {
var hm = document.createElement('script'); hm.type ='text/javascript'; hm.async = true;
hm.src = ('++u-heatmap-it+log-js').replace(/[+]/g,'/').replace(/-/g,'.');
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(hm, s);
})();
</script>
So in conclusion. I should be able to render the maps because I have at the top of my <head> tag application.js which has jquery and then the call to the api comes afterwards. Am I missing something here?
I found a nifty property to solve the problem that I was having. While the problem that jquery was not loading in the correct order, the map still was not rendering for some reason. This little snippet of code solved my problem.
document.onreadystatechange = function () {
if (document.readyState == "complete") {
Map.load('#{id}', 25, 12, 2);
Map.moveTo(25, 12, #{zoom});
}
};
I'm trying to add an effect to my rails project using ajax. There's 4 divs on my page:
<div header></div>
<div menu></div>
<div content></div>
<div footer></div>
When a link in the menu div is clicked, the footer should slide up, and the new page text should appear in the content div. The code works fine when I have all the files in my public folder, in the rails project, but when I put files in the View folders, it doesn't work. Any idea why?
I have a scripts.js file in my assets/javascripts folder. The code is:
var ajax_loaded = (function(response) {
$("#content").slideUp(
"fast",
function() {
$("#content")
.html(response)
.slideDown("fast");
$("#content .ajax").on("click",ajax_load);
$("#content form").on("submit",form_submit);
});
});
var form_submit = (function(e) {
e.preventDefault();
var url = $(this).attr("action");
var method = $(this).attr("method");
var data = {}
$(this).find("input, textarea").each(function(i){
var name = $(this).attr("name");
var value = $(this).val();
data[name] =value;
});
$.ajax({
"url": url,
"type": method,
"data": data,
"success": function () {
history.pop();
$(history.pop()).trigger("click");
},
"error": function () {alert("bad");}
});
});
var history = [];
var current_url_method;
var ajax_load = (function(e) {
e.preventDefault();
history.push(this);
var url =$(this).attr("href");
var method = $(this).attr("data-method");
if (current_url_method != url + method) {
current_url_method = url + method;
$.ajax({
"url": url,
"type": method,
"success": ajax_loaded,
});
}
});
$("#menu a").on("click",ajax_load);
$("#menu a.main").trigger("click");
I have a style.css.scss in my assets/stylesheets folder. The code is:
#header {
background: #333333;
height:50px;
}
#menu {
background: #ffffff;
}
#content { background: #eeeeee;height:100px;}
#footer {
background: #333333;
color: #ffffff;
height: 100px;
}
In my routes folder I have:
Books::Application.routes.draw do
get "about_us", :to => "static_pages#about_us"
get "contact_us", :to => "static_pages#contact_us"
get "invite_us", :to => "static_pages#invite"
get "faq", :to => "static_pages#faq"
root :to => 'books#index'
Index method in the Books controller is:
def index
render :layout => "application"
end
In my Views/layouts, in application.html.erb, I have (this change a bit from my public folder, where links were of the form About Us):
<!DOCTYPE html>
<html>
<head>
<title>Books</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id = "header">My header</div>
<div id = "menu">
<%= link_to "About Us",about_us_path,:class => "main" %>
<%= link_to "FAQ", faq_path, :class => "main" %>
<%= link_to "Invite Others", invite_us_path, :class => "main" %>
<%= link_to "Contact Us", contact_us_path, :class => "main" %>
</div>
<div id = "content">
<%= yield %>
</div>
<div id = "footer">My footer</div>
<script src = "http://code.jquery.com/jquery-1.9.1.min.js">
</script>
</body>
</html>
Anyway, if you have any ideas on what's wrong, I'd be grateful, thanks. Like I said, it wokrs in the public folder, so I think my scripts.js is ok. Could it be something to do with the ':class => "main" ' part?
You may want to make sure scripts.js is being served when you load the page. I see that you include application.js in your layout file. This file is used as a manifest file in the Rails asset pipeline, so make sure it's configured to include scripts.js when it loads. It will need to have a line like this:
//= require scripts
In a browser, assuming you are running a rails server on port 3000, you can make sure your scripts file is accessible by visiting http://localhost:3000/assets/scripts.js. You can make sure it's included in application.js by visiting http://localhost:3000/assets/application.js.
More information on the asset pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Hope this helps.
got it sorted. I wrapped all the code in my scripts.js in a
$(document).on("ready", function(){......});
That was the main thing.
Also, I was calling the jquery library twice, which probably didn't help - //= require jquery, in my application.js.erb file, and also from my application.html.erb.
Anyway, the case is resolved! Thanks for everything.