Is it possible to access and modify the JSX used to create a React class and re-transpile it on the fly. For example, if you had the following:
var Item = React.createClass({
render: function () {
return <div>Hello</div>
}
}
How could we: access the raw JSX, modify it to return something else, then transpile so the changes could be seen?
YES I understand this could be extremely dangerous.
It's probably easier to compile the JSX to JS first, and then use something like esprima to make the changes you need on the AST esprima gives you. Then pretty print it to JS again.
But I have to ask what your use case is, because it doesn't seem like the very best of ideas.
Related
I have been struggling about finding a method to get the name of a React component. I tried the this.constructor.name property. However learnt that it doesn't work in production mode since Webpack minifies the code. Is there any way I can use without any caveats?
It's up on from where you need the name. If you can treat the component as a function (with hooks) you could just call Function.name
function MyComponent({giveNameToParent}){
const sendInfo(){
giveNameToParent(MyComponent.name);
}
return (
<div onClick={sendInfo}>Hi there!</div>
);
}
I don't know if this can solve your problem, but if you need something more solid, please give more context to your case.
Source:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
I've been playing with reactjs a bit lately. As I understand, ReactJS will convert html elements into something like this:
<div><span>Text</span></div>
Into
React.createElement('div', null, React.createElement('span', null, 'Text'))
So far so good, but what I'd like to do is have special component that will override all html elements so some kind of basic behavior can be added to any html element using props.
So instead of the above, I'd want something like this instead:
React.createElement(Html('div'), null, React.createElement(Html('span'), null, 'Text'))
This would make it possible to make usual html a bit more dynamic without having to define a new component each time. One alternative in my mind to this would be to have Component based on the props. So adding a particular prop would give a particular trait to the component above.
One example would be to have something like this:
<div foreach="collection" as="value">
<div my-value="value" />
</div>
Just to explain a bit further, the idea is mainly to adapt React to different templating engines. So it's not necessary to rewrite completely templates to work in React and gives some kind of compatibility layer between a legacy templating engine.
So rewriting as this isn't what I'm asking. I already know how to write directly for React.
<div>
{
this.collection.map((value) => {
return <div>{value}</div>
})
}
</div>
One alternative for me which would be quite easy if React can't do that easily is to transform the html elements as Uppercase or differently so React thinks it's a component instead of html.
I'm trying to write a vue plugin with custom options. I followed the official vue guidelines (https://v2.vuejs.org/v2/guide/plugins.html) on doing so but can't find a way to define custom options. These options should be read by normal javascript which then exports an object that is used by a vue component.
My folder structure is like this:
/src
factory.js
CustomComponent.vue
factory.js
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = new ImgixClient({
domain: CUSTOM_OPTION_URL <-- important bit
domain: Vue.prototype.$imgixBaseUrl //tried it like this
});
export { imgixClient };
I already tried to set this custom bit by utilizing Vue.prototype in the install method like this but can't seem to get it working
export function install(Vue, options) {
if (install.installed) return;
install.installed = true;
Vue.prototype.$imgixBaseUrl = options.baseUrl;
Vue.component("CustomComponent", component);
}
I'm afraid this isn't going to be the simple answer you might have been hoping for... there's a lot to unpick here.
Let's start with factory.js. That is not a factory. It's a singleton. Singletons have problems around dependencies, configuration and the timing of instantiation and that's precisely the problem you're hitting. More on that later.
Now let's take a look at the plugin. First up, these two lines:
if (install.installed) return;
install.installed = true;
That shouldn't be necessary. Vue already does this automatically and should ensure your plugin is only installed once. Perhaps this came from an old tutorial? Take a look at the source code for Vue.use, there's not a lot to it:
https://github.com/vuejs/vue/blob/4821149b8bbd4650b1d9c9c3cfbb539ac1e24589/src/core/global-api/use.js
Digging into the Vue source code is a really good habit to get into. Sometimes it will melt your mind but there are some bits, like this, that aren't particularly difficult to follow. Once you get used to it even the more opaque sections start to become a little clearer.
Back to the the plugin.
Vue.prototype.$imgixBaseUrl = options.baseUrl;
It is not clear why you are adding this to the prototype.
I'm going to assume you are already familiar with how JavaScript function prototypes work.
Component instances are actually instances of Vue. So any properties added to Vue.prototype will be inherited by your components with almost no overhead. Consider the following simple component:
<template>
<div #click="onClick">
{{ $imgixBaseUrl }}
</div>
</template>
<script>
export default {
methods: {
onClick () {
const url = this.$imgixBaseUrl
// ...
}
}
}
</script>
As $imgixBaseUrl is an inherited property it is available within onClick via this.$imgixBaseUrl. Further, templates resolve identifiers as properties of the current Vue instance, so {{ $imgixBaseUrl }} will also access this.$imgixBaseUrl.
However, if you don't need to access $imgixBaseUrl within a component then there is no need to put it on the Vue prototype. You might as well just dump it directly on Vue:
Vue.imgixBaseUrl = options.baseUrl;
In the code above I've ditched the $ as there's no longer a risk of colliding with component instance properties, which is what motivates the $ when using the prototype.
So, back to the core problem.
As I've already mentioned, singletons have major problems around creation timing and configuration. Vue has its own built-in solution for these 'do it once at the start' scenarios. That's what plugins are. However, the key feature is that plugins don't do anything until you call install, allowing you to control the timing.
The problem with your original code is that the contents of factory.js will run as soon as the file is imported. That will be before your plugin is installed, so Vue.prototype.$imgixBaseUrl won't have been set yet. The ImgixClient instance will be created immediately. It won't wait until something tries to use it. When Vue.prototype.$imgixBaseUrl is subsequently set to the correct value that won't have any effect, it's too late.
One way (though not necessarily the best way) to fix this would be to lazily instantiate ImgixClient. That might look something like this:
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = null;
export function getClient () {
if (!imgixClient) {
imgixClient = new ImgixClient({
domain: Vue.prototype.$imgixBaseUrl
});
}
return imgixClient;
}
So long as nothing calls getClient() before the plugin is installed this should work. However, that's a big condition. It'd be easy to make the mistake of calling it too soon. Besides the temporal coupling that this creates there's also the much more direct coupling created by sharing the configuration via Vue. While the idea of having the ImgixClient instantiation code in its own little file makes perfect sense it only really stands up to scrutiny if it is independent of Vue.
Instead I'd probably just move the instantiation to within the plugin, something like this:
import ImgixClient from "imgix-core-js";
export default {
install (Vue, options) {
Vue.imgixClient = Vue.prototype.$imgixClient = new ImgixClient({
domain: options.baseUrl
});
Vue.component("CustomComponent", component);
}
}
I've made a few superficial changes, using a default export and wrapping the function in an object, but you can ignore those if you prefer the way you had it in the original code.
If the client is needed within a component it can be accessed via the property $imgixClient, inherited from the prototype. For any other code that needs access to the client it can either be passed from the component or (more likely) grabbed directly from Vue.imgixClient. If either of these use cases doesn't apply then you can remove the relevant section of the plugin.
I know this is not the React way of doing things but it's something i need and i just wanna try a solution.
After an action in my page, i display a JSON with
JSON.stringify(myJSON, null, 4);
This change triggers the render in my class.
Inside this json i have a timestamp value, that i translate into readable time. I do this before stringifying, like this:
myJson.timestamp = `${myJson.timestamp} ( ${newDate(parseInt(myJson.timestamp) * 1000)} )`
Now comes the weird stuff:
I need to have a button right next to this translated timestamp. In the previous version of my app i was doing this easily like this:
myJson.timestamp = myJson.timestamp + "( " + newDate(parseInt(myJson.timestamp) * 1000) + " ) <button>action!</button>"
which rendered my button in the page.
But react just writes it as a string, without rendering it. I guess this happens because ReactDOM doesn't parse that as a new HTML element because render is not triggered anywhere.
I would like to know if it is possible to do something like this in react and how. As you can see it's pretty complicated to try to render the button in that place the react way and i have no idea how to actually do it.
#AnaLizaPandac gave me the proper solution to this problem. dangerouslySetInnerHTML was what i am looking for, but i want to take a moment to go a bit deeper into why this solution is usually wrong and why i actually need it. Here you can find the docs for this property.
As you should know, innerHTML is bad because it exposes your web pages to XSS attacks. Why would i employ this solution though? This page does not contain any shared information with any other user and it doesn't contain any vulnerable inputs. I just needed to decorate my JSON object with a button next to the timestamp, that when clicked, redirects to another public page (like google.com). I believe this behavior doesn't expose my app to XSS and solves my problem in a simple elegant way.
I hope i am now wrong with regard to how dom-based XSS works, in case i misunderstood something, leave a comment.
Edeph,
I may be misdiagnosing the problem you are experiencing since I cannot see the entire React component that is misbehaving.
However, based on what you described, I believe the problem is that you are colocating both JSX (i.e., <button>action!</button>) and javascript code. This is certainly doable and a common usecase for React; however, it would need to be done some way similar to this:
myJson.timestamp = myJson.timestamp + "( " + newDate(parseInt(myJson.timestamp) * 1000) + " )
const MyButton () {
return (
<div>
{myJson.timestamp}
<button>action!</button>
</div>
);
}
The key here is JSX expressions should always be enclosed in parens, and JS expressions colocated with JSX need to be enclosed in curly brackets.
I hope this is helpful.
Rendering components from an array
If you have an array of timestamps, you can render the button component like so:
return myTimestamps.map(timestamp => {
const myButton () {
return (
<div>
{timestamp}
<button>action!</button>
</div>
);
}
}
This will result in a column of timestamps, each with a button.
I would actually like to ask a question in the comments to give you a full answer but I've still not enough reputation to do so.
You have to use the following attribute: dangerouslySetInnerHTML={{__html: {yourHtmlAsString}} like this:
render: function() {
return (
<div className="content" dangerouslySetInnerHTML={{__html: thisIsMyCopy}}></div>
);
}
Here's a working sample:
https://codesandbox.io/s/v8x56yv52l
Having said that, there are ways to circumvent this problem in most cases, unless you return dynamically rendered HTML from your server-side app that can differ in its structure a lot. But if the structure of the HTML is mostly the same and only the content changes, you can dynamically render or omit parts of your JSX like this:
return (
{props.conditionMet ? <div>props.anyContentYouLike</div> : null}
);
You can also build on the above idea and come up with much more complicated solutions:
render () {
innerHTML = props.conditionMet ? <div>props.anyContentYouLike</div> : null};
return innerHTML;
}
Hope that helps.
I am struggling with global variables as I want some variables which I need to access in all the components so what should i do in angular 4.
I've tried something like this:
Created one file with the name of global.ts and made a class with the name GlobalComponent something like this
export class GlobalComponent {
globalVar:string = "My Global Value";
}
and am using it on HeaderComponent by importing and making instance and it's working fine but this is very long process to import in each and every files to get it available.
So I want it to be available on all the components without importing it.
Is there any way or trick to achieve this? Any suggestion would be appreciated :)
As #bgraham is suggesting, it is definitely better to let angular injector to instantiate the service.
But you could also export just a simple object with key-value pairs, including functions. Then you can simply import it and use it (without the instantiation part).
globals.ts file:
export const GLOBALS = {
globalVar: 'My Global Value',
globalFunc: () => alert('123')
};
your.component.ts file:
import { GLOBALS } from './globals.ts';
console.log(GLOBALS.globalVar);
GLOBALS.globalFunc();
Btw I don't think there is a way how to get rid of the import statement - that is part of how typescript works...
I don't think what you want to do is possible and it's probably not a good idea.
The import statements are how webpack (or other bundlers) are able to build a tree to figure out which files to include. So it might be tricky to get global files built into all your bundles.
Also I would add, I'm not sure just importing the static file is the way to go either. It's kind of quick and dirty, which maybe is what you want I guess, but for production apps I would recommend making an angular service and injecting it.
export class GlobalVariablesService {
globalVar:string = "My Global Value";
}
This way you can mock these for unit tests or potentially pass in different variables depending on your changing needs in the future.
If you need these to update and push that into lots of components throughout your app, you might look into Redux. Its pretty handy for that kind of thing.
Sorry, perhaps not the answer you were hoping for
Simply create constant file - constant.ts under src folder.
Now import constant.ts file whenever you require parameter to be called
It will look like so
export const constant = {
COSNT_STRING : 'My Global Value',
}
how to use constant:
1) Import file.
2) constant.CONST_STRING
Also this is a good practice from future prospective, if you want to modify response just made change in one file not in 800 files.