React + Rails with Redux Persist Gate - ruby-on-rails

I am new to everything as specially to react_on_rails and I am trying to figure out how to use redux-persist with my project so that when I changing the page, I am not losing any redux store. I have figure out the redux setup but I cannot get Redux Persist to work right and it still fails to Uncaught Error: Could not find store registered with name 'rootStore'. Registered store names include [ ]. Maybe you forgot to register the store? I just wondering if anyone can help with it to fix a problem. I tried to go over the documentation several times and did really helped me with the option that I have.
my application view
<!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>
My entry point to register ProductDetailPage
import ReactOnRails from 'react-on-rails';
import ProductDetailPage from '../pages/ProductDetailPage';
import { registerRootStore } from '../utils/ReactOnRails';
ReactOnRails.setOptions({
traceTurbolinks: true,
});
ReactOnRails.register({ProductDetailPage});
registerRootStore();
utils/ReactOnRails
import { configureStore } from '../store/rootStore';
export default function getReactOnRails() {
window.ReactOnRails = window.ReactOnRails || require('react-on-rails').default;
return window.ReactOnRails;
}
export const registerRootStore = function () {
if (getReactOnRails().storeGenerators().has('rootStore')) {
return;
}
getReactOnRails().registerStore({rootStore: configureStore });
};
store/rootStore
import { createStore } from 'redux';
import reducerIndex from '../reducers/index';
import { persistReducer} from 'redux-persist';
let _store;
let _persistor;
export const configureStore = function (props) {
if (_store) return _store;
const initialState = (props) ? {...props} : {};
_store = createStore(reducerIndex, initialState);
_persistor = persistReducer(_store);
return _store;
};
export const getPersistor = function () {
return _persistor;
};
reducers/index
import { combineReducers } from 'redux';
import { persistReducer} from 'redux-persist';
import cartReducer from './cartReducer';
const rootReducer = combineReducers({
cart: cartReducer,
});
const reducer = persistReducer(
{
key: 'root',
storage: require('localforage'),
},
rootReducer
);
export default reducer;
And the last file which handles all other components that will be later injected with.
// #flow
import * as React from 'react';
import { Provider } from 'react-redux';
import getReactOnRails from '../utils/ReactOnRails';
import { PersistGate } from 'redux-persist/es/integration/react';
import { getPersistor } from '../store/rootStore';
type Props = {
children: React.Node,
loading?: React.Node,
}
const rootStore = getReactOnRails.getStore('rootStore'); // another error that happening for me, it says that getStore is not a function.
export default class ProviderGate extends React.Component<Props> {
render() {
const persistor = getPersistor();
if (!persistor) return this.props.children;
return <Provider store={rootStore}>
<PersistGate persistor={persistor} loading={this.props.loading}>
{this.props.children}
</PersistGate>
</Provider>;
}
}

After a couple hours of debuging I actually find why my persistStore was not working. Here the things I did in my code.
store/rootStore
_persistor = persistReducer(_store); => _persistor = persistStore(_store);
Main Entry file
ReactOnRails.register({ProductDetailPage});
registerRootStore();
Should be
registerRootStore();
getReactOnRails().register({ProductDetailPage});
and finally a component that responds to PersistGate and redux Provider it should be rendered in the component, not outside of the class and it should be like this
const rootStore = getReactOnRails.getStore('rootStore');

Related

Vue3 doesn't seem to be loaded in Rails 7 + shakapacker app

I created a new project for a shopify app with rails 7 and shakapacker. I want to use Vue components in my .slim files. The problem is that Vue doesn't seem to be loaded in my app, although I don't get any errors.
Here is what I did:
// config/webpack/rules/vue.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
extensions: [
'.vue'
]
}
}
// config/webpack/webpack.config.js
const { webpackConfig, merge } = require('shakapacker')
const vueConfig = require('./rules/vue')
module.exports = merge(vueConfig, webpackConfig)
// app/javascript/packs/application.js
import HelloWorld from '../components/HelloWorld'
import { createApp } from 'vue'
const app = createApp({
el: '#app'
})
app.component('helloworld', HelloWorld)
document.addEventListener('DOMContentLoaded', () => {
app
})
// app/javascript/components/HelloWorld.vue
<template>
<h1>Hello world</h1>
</template>
<script>
export default {
name: 'HelloWorld'
}
</script>
/ app/views/layouts/embedded_app.slim
doctype html
html[lang="en"]
head
meta[charset="utf-8"]
- application_name = ShopifyApp.configuration.application_name
title
= application_name
= stylesheet_link_tag "application", "data-turbo-track": "reload"
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
= csrf_meta_tags
body
#app
.wrapper
main[role="main"]
= yield
= content_tag(:div, nil, id: 'shopify-app-init', data: { api_key: ShopifyApp.configuration.api_key,
shop_origin: #shop_origin || (#current_shopify_session.shop if #current_shopify_session),
host: #host,
debug: Rails.env.development? })
And finally, the view where I just want to display the HelloWorld.vue component:
/ app/views/home/index.slim
helloworld
However, nothing is displayed and I have no errors. I tried to modify the creation of the app in this way, to see if the log appears:
// app/javascript/packs/application.js
import HelloWorld from '../components/HelloWorld'
import { createApp } from 'vue'
const app = createApp({
el: '#app',
created () {
console.log('ok')
}
})
app.component('helloworld', HelloWorld)
document.addEventListener('DOMContentLoaded', () => {
app
})
but then again, I have nothing in console, so I'm not even sure that the app is well rendered. On the other hand, I checked that the DOMContentLoaded event is indeed triggered and it is.
I'm not very comfortable with webpack so I don't know if something is wrong with my configuration, I followed shakapacker's README.
I don't think this is related, but the app is rendered in a Shopify test store via an Ngrok tunnel.
I don't know where to look anymore... Does anyone have an idea?
Thanks in advance
I haven't written any VueJS in a long time, but this is usually what I do in my application.js using React & Shopify Polaris components.
function initialize() {
const rootElement = document.getElementById('app')
const root = createRoot(rootElement);
/* some app bridge code I removed here */
/* react 18 */
root.render(
<BrowserRouter>
/* ... */
</BrowserRouter>
)
}
document.readyState !== 'loading' ? initialize() : document.addEventListener('DOMContentLoaded', () => initialize())
If your <div id="app"> is EMPTY when inspected with browser tools, my first guess would be you're creating an instance, but not actually rendering it in the end.
An application instance won't render anything until its .mount() method is called.
https://vuejs.org/guide/essentials/application.html#mounting-the-app
I would've commented to ask first, but I don't have enough reputation points to do so

useEffect / useState / setInterval React on Rails components not rendering

I am trying to use useEffect, setInterval and useState in order to cycle through fontawesome icons at a timed interval. I am very new to react and I am not getting any errors my component and all of the components below it are just not rendering. I am using the react-rails gem at its most recent version
here is the code for the component:
import React, {useEffect, useState} from "react"
import PropTypes from "prop-types"
function Changing(){
const mobileData = {
icon: "fas fa-mobile-alt",
caption: "Mobile Applications",
style: ""
}
const webData = {
icon: "fas fa-desktop",
caption: "Web development",
style: ""
}
const internetData = {
icon: "fas fa-wifi",
caption: "& Everything Internet",
style: ""
}
const data = [mobileData, webData, internetData];
const [iterable, setIterable] = useState(0);
const [iconData, setIconData] = useState(mobileData);
function changeIterable(){
if(iterable === 0){
setIterable((prevIterable) => prevIterable + 1)
}
else if(iterable === 1){
setIterable((prevIterable) => prevIterable + 1)
}
else{
setIterable((prevIterable) => prevIterable - 2)
}
}
useEffect(() => {
const intervalID = setInterval(() =>{
changeIterable();
setIconData(data[iterable])
}, 4000);
return () => clearInterval(intervalID)
})
return (
<React.Fragment>
<div className="row">
<div className="col-md-6">
<i className={iconData.icon} style={iconData.style} />
</div>
<div className="col-md-6">
<h3 style={iconData.style}>{iconData.caption}</h3>
</div>
</div>
</React.Fragment>
);
}
export default Changing
and I am rendering the component with:
<%= react_component "Personal" %>
<%= react_component "Changing" %>
<%= react_component "Stack" %>
Personal and Stack components were correctly rendering, but once I added Changing every component under it would not render.
I am pretty new rails and even more of a n00b when it comes to react, I was wondering if useEffect, setInterval and useState are even supported in react-rails. Any help is welcomed, thank you!
Firstly you are setting the style to an empty string when instead you'd want to set the style to an empty object {} in iconData as this is JSX. As far as next component not rendering, there could be either CSS or logic causing it. Best way to debug is to just verify with a simple component that returns a vanilla <p>Test this</p> to see why the next component is not showing, but I have a feeling that Stack has logic somewhere that returns nothing.

Why is my tracks array empty in my presentational component?

I've been working on this bug for a few hours now, and I think I've narrowed it down to these pieces of code:
// my track detail container:
import { connect } from 'react-redux';
import TrackDetail from './track_detail';
import { selectTracksFromPlaylist } from '../../reducers/selectors';
const mapStateToProps = (state, { playlistId }) => ({
tracks: selectTracksFromPlaylist(state, state.entities.playlists[playlistId])
});
export default connect(mapStateToProps)(TrackDetail);
// my track detail presentational component:
import React from 'react';
import TrackIndexItem from './track_index_item';
const TrackDetail = ({ tracks }) => (
<ul>
{ tracks.map(track => <TrackIndexItem key={track.id} track={track} />) }
</ul>
);
export default TrackDetail;
// my selector that I'm using to select all tracks from a playlist:
export const selectTracksFromPlaylist = (state, playlist) => (
playlist.track_ids.length > 0 ? playlist.track_ids.map(id => state.entities.tracks[id]) : []
);
// the component that's rendering my TrackDetail:
import React from 'react';
import { Route } from 'react-router-dom';
import TrackDetail from '../track/track_detail_container';
class PlaylistDetail extends React.Component {
componentDidMount() {
this.props.requestSinglePlaylist(this.props.match.params.playlistId);
}
render() {
const { playlist } = this.props;
if (!playlist) return null;
return (
<div className="playlist-detail-container">
<div className="playlist-detail-header">
<p>Playlist</p>
<h1>{ playlist.title }</h1>
<p>
Created by <span>{playlist.user}</span> • {playlist.track_ids.length} songs
</p>
</div>
<div className="playlist-detail-tracks">
<TrackDetail playlistId={ playlist.id } />
</div>
</div>
);
}
}
export default PlaylistDetail;
This happens every time I try accessing the page normally (without physically doing a refresh; if I refresh the screen after this error pops up, it works):
Uncaught TypeError: Cannot read property 'id' of undefined

NativeScript WebView loading local resources in src document

I am loading a local html file as the src for a NativeScript WebView component. Contained within the html file are script tags which reference javascript files that are also local resources (bundled within the app). The html file loads into the WebView just fine, but the referenced script file (mylib.js) does not.
I suspect a pathing problem but I have tried almost every variation I can think of to no avail.
My project is actually a NativeScript-Vue project and is as follows:
App.vue
<template>
<Page #loaded="onPageLoaded">
<ActionBar title="Welcome to WebView"/>
<GridLayout>
<WebView ref="myWebView" row="0" col="0"
:src="filePath" #loadFinished="onWebViewLoaded" />
</GridLayout>
</Page>
</template>
<script>
import * as fs from "tns-core-modules/file-system"
import * as utils from "utils/utils"
export default {
data() {
return {
filePath: ''
}
},
methods: {
onPageLoaded () {
this.setLocalIndexFilePath()
},
onWebViewLoaded (event) {
if (event.error) {
console.log(error)
} else {
console.log('webview loaded')
}
},
setLocalIndexFilePath () {
const deviceName =
utils.ios.getter(UIDevice, UIDevice.currentDevice).name
// iPhone 6 is the name of my simulator
if (deviceName == 'iPhone 6') {
const webViewSRC =
encodeURI(`${fs.knownFolders.currentApp().path}/www/index.html`)
this.filePath = webViewSRC
console.log(webViewSRC)
} else {
this.filePath = "~/www/index.html"
}
}
}
}
</script>
index.html
<!doctype html>
<head>
<script src="./mylib.js" type="text/javascript"></script>
<script type="text/javascript">
function onBodyLoaded() {
var msg = document.getElementById('msg');
msg.insertAdjacentHTML('beforeend', '<br />body loaded!');
}
function onLocalButtonClicked() {
var msg = document.getElementById('msg');
msg.insertAdjacentHTML('beforeend', '<br />local: You clicked button!');
}
</script>
</head>
<html>
<body onload="onBodyLoaded()">
<Button onclick="onLocalButtonClicked()">Click Me</Button>
<Button onclick="onButtonClicked()">Click Me to test external js</Button>
<p id="msg">Debug:</p>
</body>
</html>
mylib.js
// This function never gets called
function onButtonClicked() {
var msg = document.getElementById('msg');
msg.insertAdjacentHTML('beforeend', '<br />external js file: You clicked button!');
}
webpack.config.sys
...
// Copy assets to out dir. Add your own globs as needed.
new CopyWebpackPlugin([
{ from: "fonts/**" },
{ from: "**/*.+(jpg|png)" },
{ from: "assets/**/*" },
{ from: "www/**/*" },
...
This is a known issue with iOS. There is a patch work you could try, I had implemented the same in Playground for a similar issue, its applicable for Vue too.

How to pass from mvc to angular 2 and type script?

I need some help.I am new to angular. What i want is to pass some data (string variable 'element') to my UI and then I want to use it. I do not know how to pass it inside main.js and further. How can I pass variable to typescript file from my mvc server?
Index.cshtml:
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<app-root>Loading...</app-root>
My layout file:
#{
var elementServer = CamComponentGenerator.Api.App.elementID;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#ViewBag.Title - Cam Component Generator for Onshape</title>
<script src="~/node_modules/core-js/client/shim.min.js"></script>
<script src="~/node_modules/zone.js/dist/zone.js"></script>
<script src="~/node_modules/systemjs/dist/system.src.js"></script>
<script src="~/Scripts/jquery-3.1.1.js"></script>
<script src="~/Scripts/systemjs.config.js"></script>
<script>
System.import('../ngapp/.compiled/main.js').catch(function (err)
{
console.error(err);
});
</script>
<script>
var element = "#elementServer";
</script>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
#RenderBody()
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
#Scripts.Render("~/Scripts/ccg-script.js")
#RenderSection("scripts", required: false)
</body>
</html>
My main.ts file:
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
and my app.ts file:
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { ParamsComponent } from './app/ccg-disk-cam-params/disk-cam-
params.component';
import { CenterComponent } from './app/ccg-center/ccg-center.component';
import { ResultsComponent } from './app/ccg-results/ccg-results.component';
import { OutputComponent } from './app/ccg-output/ccg-output.component';
import { CylinderTranslatingComponent } from './app/ccg-disk-cam-follower-
params/cylinder-translating.component';
import { CylinderSwingingArmComponent } from './app/ccg-disk-cam-follower-
params/cylinder-swinging-arm.component';
import { SphereTranslatingComponent } from './app/ccg-disk-cam-follower-
params/sphere-translating.component';
import { SphereSwingingArmComponent } from './app/ccg-disk-cam-follower-
params/sphere-swinging-arm.component';
import { ModalComponent } from './app/ccg-modal/ccg-modal.component';
import { HttpModule } from '#angular/http';
#NgModule({
imports: [BrowserModule, HttpModule, FormsModule ],
declarations:
[
AppComponent,
ParamsComponent,
CenterComponent,
ResultsComponent,
OutputComponent,
CylinderTranslatingComponent,
CylinderSwingingArmComponent,
SphereSwingingArmComponent,
SphereTranslatingComponent,
ModalComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
You can create a global javascript variable, or you can use local storage.
GlobalVariable:
Before boostrapping angular:
<script>
var element = "#elementServer";
var globalVariable = element;
</script>
When you need to use the variable in your component declare is as any:
declare var globalVariable: any;
And you can then use it:
var myGlobal = globalVariable;
Using LocalStorage:
This is more of a workaround that I've used before. You can pass the razor variable to javascript as you are doing, then save it in localStorage. Then you angular components can retrieve the value from localStorage.
Both these approaches are not recommended for sensitive data.
So before you bootstrap angular2:
<script>
var element = "#elementServer";
localStorage.setItem('elementServer',element);
</script>
then you can retrieve the item in your angular component:
var lstorage = localStorage.getItem("elementServer");
Plunker example

Resources