Isomorphic JS - variable is not available on client - javascript

I'm currently working on a project which uses some shared JS between client and server. The tech stack includes Node v6, Webpack, React etc.
There's a directory "shared" from which server and client require a file called rules.js. On the first render which happens on the server side, the rules variable declared in rules.js is set with a value from DB (I've done console.log to verify it's really filled with data).
Then on client side some component might require this rules.js file and get the rules variable. However, when I console.log the variable, it's empty.
The rules file looks similar to this:
// shared/rules.js
let rules;
// This is called on server to set the value
exports.setData = function(data) {
rules = data;
}
exports.rules = rules;
Do you have any idea what could be wrong? Should I choose different approach?

While the file may be shared by both the client and server, the instance of the class is not. So, anything you do to it on the server will not persist on the client, and vice versa.
One idea available is that of "dehydrating" and "rehydrating" your state.
server.js
// implemented in server to dehydrate your state
import rules from '../shared/rules';
let dehydratedRules = JSON.stringify(rules.rules);
let html = renderToString(<App/>);
// now in your render call
res.render('yourTemplateFile', {html, dehydratedRules});
yourTemplateFile
...
<script type="text/javascript">var __RULES__ = {{dehydratedRules}};
...
client.js
// implemented in client to rehydrate your state
import rules from '../shared/rules';
let parsedRules = JSON.parse(__RULES__);
delete window.__RULES__;
// set the rehydrated data back in the class
rules.setData(parsedRules);
Checkout this SO answer utilizing an analogy from Back to the Future to explain dehydrating/rehydrating.

Related

How to call a function inside a Template literal in node js

I am using node js , express. I am trying to call a function in template literal.
I made a module and import in main file and then add the function to app.locals and accessing in my views that works fine. But when i try to use it in template literals and also in my public file it give me error "currencyToWords is not defined".
I think in public file it makes sense because i make public folder static.
but in my views it is weird because when i use it in html it works but when i use it in template literal it gives error.
I want to use the currencyToWords function in template literals to change the response.
I have one solution that i can make the file in public folder and add the function in script in that file and import in footer but i dont want to do that.
Can anybody help me in solving the issue.
My app stucture
module.exports = { currencyConverter }
In app.js
app.js is my main file where i create server
var { currencyToWords } = require('./config/functions');
app.locals.currencyToWords = currencyToWords;
by adding in locals i can access it in my application any where
This code is working:
<div class="fix pull_right">
<h3><%= currencyToWords(property.propertyDetails.price)%></h3>
</div>
This is not working
var a =`
<p>${currencyToWords(property.propertyDetails.price)}</p>
`;
the above code is same but only difference is first one is use in ejs tags and second in ejs and template literals.
Here is the documentation from express about app.locals
app.locals
The app.locals object has properties that are local variables within the application.
console.dir(app.locals.title)
// => 'My App'
console.dir(app.locals.email)
// => 'me#myapp.com'
Once set, the value of app.locals properties persist throughout the life of the application, in contrast with res.locals properties that are valid only for the lifetime of the request.
You can access local variables in templates rendered within the application. This is useful for providing helper functions to templates, as well as application-level data. Local variables are available in middleware via req.app.locals (see req.app)
app.locals.title = 'My App'
app.locals.strftime = require('strftime')
app.locals.email = 'me#myapp.com'

Using eval to dynamically render JSX served from backend

I'm working on a React frontend that gets data from a python JSON API. One section of my website has premium content and is reserved for paying users; currently I ensure that other users don't have access to it by fetching the content as a JSON object and converting it to JSX on the frontend according to a certain convention. For example:
{
{ 'type': 'paragraph', 'value': 'some text'},
{ 'type': 'anchor', 'href': 'some url', 'value': 'some description'}
}
would be rendered as :
<p>some text</p>
some description
Not surprisingly, things started to get pretty complicated as the content began to get more structured, simple things like making part of the text bold require a disproportional amount of effort.
As a potential solution, I had this idea: instead of sending the content as an object and parsing it, why not send a string of JSX and evaluate it on the frontend?
I started like this:
import * as babel from "#babel/standalone";
export function renderFromString(code) {
const transformed_code = babel.transform(code, {
plugins: ["transform-react-jsx"]
}).code;
return eval(transformed_code);
}
I imported this function in my premiumContent page and tried passing a complete component as a string (with import statements, etc) but got errors because the modules can't be found. I assumed this happens because the code is being interpreted by the browser so it doesn't have access to node_modules?
As a workaround, I tried passing only the tags to renderFromString and call it in the context of my component where all the modules are already imported :
import * as babel from "#babel/standalone";
export function renderFromString(code, context) {
const _es5_code = babel.transform(code, {
plugins: ["transform-react-jsx"]
}).code;
return function() {
return eval(_es5_code);
}.call(context);
}
This also failed, because it seems that eval will still run from the local context.
Finally, I tried doing the same as above but executing eval directly in my component, instead of from my function .This works as a long as I store "React" in a variable : import ReactModule from "react";const React = ReactModule, otherwise it can't be found.
My questions are:
Is there any way I can make my first two approaches work?
I know eval is considered harmful, but since the content is always completely static and comes from my own server, I don't see how this wouldn't be safe. Am I wrong?
Is there a better solution for my problem? That is, a way to safely deliver structured content to only some users without changing my single page app + JSON api setup?
The best solution for this is React server-side rendering.
Since you need markup that is client-side compatible but at the same time dynamically generated through React, you can offload the markup generation to the server. The server would then send the rendered HTML to the client for immediate display.
Here's a good article about React SSR and how it can benefit performance.

Pass event object into template using Meteor

I am building an application for customizing the product grid order of an e-commerce website. Being able to present product order in responsive and flexible ways, would really make it easier for project managers to create a better experience for their customers.
Part of this grid order application is based on an article by Ryan Glover (to whom I owe many thanks), called Importing CSV's https://themeteorchef.com/tutorials/importing-csvs, using PapaParse.
Even though this the templates and build is in Meteor, this is a bit of a vanilla JavaScript question.
Here's my pretty stripped down question: How do I get the event object into the template? I get the event object fine from the Papa.parse() function, and could console.log(it). But, how do I pass the event object into the template.
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
// NOTE: trying to figure out how to pass event object to template
Template.upload.events({
'change [name="uploadCSV"]' ( event, template ) {
// Handles the conversion and upload
var fileInput = document.querySelector('[name="uploadCSV"]');
// Parse local CSV file
Papa.parse(fileInput.files[0], {
header: true,
complete: function(results) {
// console.log(results); // includes data, error, and misc
// NOTE: This is the data object to send to the template
let itemData = results.data;
console.log(itemData) // here is the data object
// This test correctly iterates over the object, but should be done in the template
itemData.forEach(function(item) {
console.log(item)
// console.log(item['itemcode'])
});
// HELP! How do I send the object itemData to the template?
} // END complete
}); // END parse
} // END change
}) // END events
Here is a link to the repo. https://github.com/dylannirvana/gridorderapp/tree/master/meteor/vanilla
This has got to be embarrassingly easy. Many thanks in advance!
looking on your project will make clear that this piece will not be ready to go. Your question is not somethin about events but understanding meteor. The are essentials missing.
First check your grid template, there is no data defined. Second, understand Papa.parse on client and on server via Meteor.method
Not sure how you concept should run, but lets assume, that someone should be able to upload an CSV file with data to show in page.
Is this something which is persistant data? Means, should the data be stored or will it be uploaded all the time?
In case of persistant, you should use Meteor.method and a publish and subscribe collection from server to client. You may have a look at the meteor blaze todo app tutorial.
In case of temporary data, you may create a client only minimongo collection and subscribe the client to that.
While getting data from Parser you have to insert or add that content to your local or global collection.
When doing this and having a reactive (subscribed) template to the collection it will automtically show its content.
Again, you should get familar with the meteor blaze todo tutorial, to understand your needs.
Hope that will guide you the right way.
Good luck
Tom

Express : Does mysql queries in app.js make sense

Does app.js get processed only once?. In that case, does it make sense to run setup queries in app.js so that I don't have to run it on every request?.
I have a table to countries and regions that I need in several requests.
I am looking at an implementation something like this https://stackoverflow.com/a/21133217/406659
No putting this in app.js does not make sense. I would create a models folder and encapsulate all of your database logic in a module within that folder. Then on startup of the app (ie on app.listen() callback) I would call a method from that module which does the setup for me. For example:
Some DB module in models
module.exports = {
setup: () => {
// Set up logic here - checking if tables exist etc
}
}
Then in app.js or equal
require <SOME DB MODULE>
app.listen(<PORT>, () => {
<SOME DB MODULE>.setup();
})
Note this is a generalised approach as I don't know the specifics of your setup - This approach just ensures encapsulated and reusable code. I've also used ES6 syntax where possible.
Hope this helps
Dylan

Javascript object persistency in Meteor js

Supposed that I created a js object like this
Cart = function Cart(){
this.contents = new Array();
}
Cart.prototype = {
add : function(obj){
this.contents.push(obj);
}
}
and put it in the project lib folder so that this object can be used project-wide.
Is it possible to use this object persistently in the template backend (js file)? For example, I declared :
Template.pageA.rendered = function(){
var smallCart = new Cart();
}
Can I use it in the template event? For example:
Template.pageA.events = {
'click button#add' : function(event){
smallCart.add('newItem'); //Is this object as same as the one in rendered?
}
}
I have been doing stuff by using Sessions but when there are a lot of operations to be done, the events will be cluttered with business logics and calculations. I want to avoid this, thus I am thinking of putting the logics into Javascript object functions.
Will this approach work? Will the object stay persistent?
You can place that code inside a Meteor.startup, so the object will persist always.
Meteor.startup(function(){
Cart = function Cart(){
this.contents = new Array();
}
Cart.prototype = {
add : function(obj){
this.contents.push(obj);
}
}
})
Is this object as same as the one in rendered? yes
From my point of view, putting the class definition of your cart into the lib folder can be good. If it's client-side code only, you can store it into a cart.js file into your client folder.
This definition will be available on client.
Then you need to store an instance of your cart. Instanciate a cart into your template can be a good idea (you don't need it before that), but your need to store a reference into you client side application scope (usually into app.js at the root of your client folder for example)
client/cart.js
Cart = function() {}....
client/app.js
myCart = null;
Into your template, you can instantiate the cart
myCart = new Cart();
If you're using your only on one template, you can store the instance directly to the template into the controller logic. Attach it to the rendered of the template is a bit wired, because you usually handle template rendering logic.
This way, a cart is going to be created for every user which come on your site and called the template. If you have a multi page app using iron router, myCart will be available whatever the page as soon as you have already create it into your template.
Be aware, in this way, if the client "refresh" the page reloading the JavaScript code, you'll loose the current cart. You need to persist the cart on local storage in order to support the refresh. Not worth in all the case, but for a shopping cart, can be usefull
Putting the Cart code in lib is correct, it will be available everywhere.
As far as smallCart is concerned you have declared it inside a function so it isn't available to the other template function.
Declare it outside the two functions and it will be available to both.
var smallCart;
Template.pageA.rendered = function(){
smallCart = new Cart();
}
Template.pageA.events = {
'click button#add' : function(event){
smallCart.add('newItem'); //Is this object as same as the one in rendered?
}
}

Categories

Resources