Compile a JSX string to a component on the fly - javascript

I've got a React application, in which I need to display an HTML Popup with some dynamic content generated by the server. This content also contains some JSX markup which I want to be rendered into real components.
<MyButton onClick={displayInfo}/>
...
async displayInfo() {
let text = await fetch(...)
console.log(text) // "<SomeComp onClick={foo}><OtherComp..... etc
let component = MAGIC(text)
ReactDom.render(component, '#someDiv')
}
Due to how my server app is structured, ReactServerDOM und hydration is not an option.
Is there a way to implement this on the client side only?

As others mentioned, this has code smell. JSX is an intermediary language intended to be compiled into JavaScript, and it's inefficient to compile it at run time. Also it's generally a bad idea to download and run dynamic executable code. Most would say the better way would be to have components that are driven, not defined, by dynamic data. It should be possible to do this with your use case (even if the logic might be unwieldy), though I'll leave the pros/cons and how to do this to other answers.
Using Babel
But if you trust your dynamically generated code, don't mind the slower user experience, and don't have time/access to rewrite the backend/frontend for a more efficient solution, you can do this with Babel, as mentioned by BENARD Patrick and the linked answer. For this example, we'll use a version of Babel that runs in the client browser called Babel Standalone.
Using Dynamic Modules
There needs to be some way to run the compiled JavaScript. For this example, I'll import it dynamically as a module, but there are other ways. Using eval can be briefer and runs synchronously, but as most know, is generally considered bad practice.
function SomeComponent(props) {
// Simple component as a placeholder; but it can be something more complicated
return React.createElement('div', null, props.name);
}
async function importJSX(input) {
// Since we'll be dynamically importing this; we'll export it as `default`
var moduleInput = 'export default ' + input;
var output = Babel.transform(
moduleInput,
{
presets: [
// `modules: false` creates a module that can be imported
["env", { modules: false }],
"react"
]
}
).code;
// now we'll create a data url of the compiled code to import it
var dataUrl = 'data:text/javascript;base64,' + btoa(output);
return (await import(dataUrl)).default;
}
(async function () {
var element = await importJSX('<SomeComponent name="Hello World"></SomeComponent>');
var root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
})();
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://unpkg.com/react#18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#18/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Adding a Web Worker
For a better user experience, you'd probably want to move the compilation to a web worker. For example:
worker.js
importScripts('https://unpkg.com/#babel/standalone#7/babel.min.js');
self.addEventListener('message', function ({ data: { input, port }}) {
var moduleInput = 'export default ' + input;
var output = Babel.transform(
moduleInput,
{
presets: [
// `modules: false` creates a module that can be imported
["env", { modules: false }],
"react"
]
}
).code;
port.postMessage({ output });
});
And in your main script:
var worker = new Worker('./worker.js');
async function importJSX(input) {
var compiled = await new Promise((resolve) => {
var channel = new MessageChannel();
channel.port1.onmessage = (e) => resolve(e.data);
worker.postMessage({ input, port: channel.port2 }, [ channel.port2 ]);
});
var dataUrl = 'data:text/javascript;base64,' + btoa(compiled.output);
return (await import(dataUrl)).default;
}
(async function () {
var root = ReactDOM.createRoot(document.getElementById('root'));
var element = await importJSX('<div>Hello World</div>');
root.render(element);
})();
This assumes React and ReactDOM are imported already, and there's a HTML element with id root.
Skipping JSX and Compilation
It's worth mentioning that if you're generating JSX dynamically, it's usually only slightly more complex to instead generate what that JSX would compile to.
For instance:
<SomeComponent onClick={foo}>
<div id="container">Hello World</div>
</SomeComponent>
When compiled for React is something like:
React.createElement(SomeComponent,
{
onClick: foo
},
React.createElement('div',
{
id: 'container'
},
'Hello World'
)
);
(See https://reactjs.org/docs/jsx-in-depth.html for more information on how JSX gets compiled and https://babeljs.io/repl for an interactive website to compile JSX to JS using Babel)
While you could run Babel server-side to do this (which would add additional overhead) or you could find/write a pared-down JSX compiler, you also probably can also just rework the functions that return JSX code to ones that return regular JavaScript code.
This offers significant performance improvements client-side since they wouldn't be downloading and running babel.
To actually then use this native JavaScript code, you can export it by prepending export default to it, e.g.:
export default React.createElement(SomeComponent,
...
);
And then dynamically importing it in your app, with something like:
async function displayInfo() {
let component = (await import(endpointURL)).default;
ReactDom.render(component, '#someDiv');
}

As others have pointed out, Browsers don't understand JSX. Therefore, there's no runtime solution you can apply that will actually work in a production environment.
What you can do instead:
Write a bunch of pre-defined components in the front-end repository. (The JSX with the server).
Write a HOC or even just a component called ComponentBuilder will do.
Your <ComponentBuilder /> is nothing but a switch statement that renders one or more of those pre-defined components from step 1 based on what ever you pass to the switch statement.
Ask the server to send keywords instead of JSX. Use the server sent keyword by passing it to the switch statement. You can see the example below.
Now your server decides what to render. You don't need a runtime solution.
Stick the <ComponentBuilder /> where ever you want to render components decided by the server.
Most importantly, you won't give away a front-end responsibility into the hands of the backend. That's just "very difficult to maintain" code. Especially if you start to bring in styles, state management, API calls, etc.
// Component Builder code
const ComponentBuilder = () => {
const [apiData, setApiData] = useState()
useEffect(() => {
// make api call.
const result = getComponentBuilderDataFromAPI()
if (result.data) {
setApiData(result.data)
}
}, [])
switch (apiData.componentName) {
case 'testimonial_cp':
return <Testimonials someProp={apiData.props.someValue || ''} />
case 'about_us_cp':
return <AboutUs title={apiData.props.title || 'About Us'} />
// as many cases as you'd like
default:
return <Loader />
}
}
This will be a lot more efficient, maintainable and the server ends up deciding which component to render.
Just make sure not to mix up responsibilities. What is to be on the front end must be on the front end.
eg: don't let the server send across HTML or CSS directly. Instead have a bunch of predefined styles on the front-end and let the server just send the className. That way the server is in control of deciding the styles but the styles themselves live on the front-end. Else, it becomes really difficult to make changes and find bugs.
The server sends a bunch of CSS properties. You have a bunch of component styles. There are a bunch of global styles. You also use Tailwind or Bootstrap. You will not know what exactly is setting the style or even who's controlling it. A nightmare to debug and maintain! Saying this from experience.
The above solution should work for you provided you can make the required changes to your architecture! Because it's working in a production environment as I write this answer.

Related

React server side component - alternative to `response.readRoot()` function

In React server components official GitHub example repo at exactly in this line here they are using response.readRoot().
I want to create a similar app for testing something with RSC's and it seems like the response does not contain the .readRoot() function any more (because they have updated that API in the react package on npm and I cannot find anything about it!). but it returns the tree in value property like below:
This means that whatever I render in my root server component, will not appear in the browser if I render that variable (JSON.parse(value) || not parsed) inside of my app context provider.
How can I render this?
Basically, if you get some response on the client side (in react server components) you have to render that response in the browser which has the new state from server but since I don't have access to readRoot() any more from response, what would be the alternative for it to use?
I used a trick o solve this issue, but one thing to keep in mind is that they are still unstable APIs that react uses and it's still recommended not to use React server component in the production level, uses it for learning and test it and get yourself familiar with it, so back to solution:
My experience was I had a lot of problems with caching layer they are using in their depo app. I just removed it. My suggestion is to not use it for now until those functions and APIs become stable. So I Removed it in my useServerResponse(...) function, which in here I renamed it to getServerResponse(...) because of the hook I created later in order to convert the promise into actual renderable response, so what I did was:
export async function getServerResponse(location) {
const key = JSON.stringify(location);
// const cache = unstable_getCacheForType(createResponseCache);
// let response = cache.get(key);
// if (response) return response;
let response = await createFromFetch(
fetch("/react?location=" + encodeURIComponent(key))
);
// cache.set(key, response);
return response;
}
and then creating a hook that would get the promise from the above function, and return an actual renderable result for me:
export function _useServerResponse(appState) {
const [tree, setTree] = useState(null);
useEffect(() => {
getServerResponse(appState).then((res) => {
setTree(res);
});
}, [appState]);
return { tree };
}
and finally in my AppContextProvider, I used that hook to get the react server component tree and use that rendered tree as child of my global context provider in client-side like below:
import { _useServerResponse } from ".../location/of/your/hook";
export default function AppContextProvider() {
const [appState, setAppState] = useState({
...someAppStateHere
});
const { tree } = _useServerResponse(appState);
return (
<AppContext.Provider value={{ appState, setAppState }}>
{tree}
</AppContext.Provider>
);
}
I know that this is like a workaround hacky solution, but it worked fine in my case, and seems like until we get stable APIs with proper official documentation about RSCs, it's a working solution for me at least!

How to use the Ace editor validator without instantiating an Ace editor instance?

I use react-ace to create a CSS text editor in my React app.
That looks something like...
import Ace from 'react-ace'
...
<Ace
mode="css"
value={value}
onChange={onValueChange}
onValidate={onValidate}
...
/>
...
This works fine and dandy—highlighting CSS syntax errors and warnings. Also, the onValidate returns the error/warning "annotations" data structure.
However there is a need, elsewhere in the application, to run the same validator used in this React Ace component, but outside of the context of this Component. Essentially I need to pass the content in value through the error/warning annotation system, but can't instantiate this react element.
I've tried the following:
import { EditSession } from 'brace'; # "brace" is the "module" compatible version of the ace editor that our "react-ace" uses
import 'brace/mode/css';
export const getCssAnnotations = (value)=> {
const editSession = new EditSession(value);
editSession.setMode('ace/mode/css');
const annotations = editSession.getAnnotations();
return annotations;
};
However, the annotations returned by this function are always []! I assume this is because I'm just accessing the annotation setter/getter interface, and not actually running the annotations creator. But I can't figure out what actually does the annotations work normally.
I've looked at docs on Creating a Syntax Highlighter for Ace, but don't understand if/why a web worker would need to be involved here.
Thanks!
This doesn't work, because editSession uses web worker to generate annotations which is async:
editSession.on('changeAnnotation', () => {
let annotations = editSession.getAnnotations();
callback(null, annotations)
});
docs
Note that currently each editSession creates a new worker, so it is better to use setValue on an existing instance of editSession, or call editSession.destroy() before calling the callback
So a full solution might look like:
const getAnnotationsPromise = (value, mode)=> {
const editSession = new EditSession(value);
editSession.setMode(`ace/mode/${mode}`);
return new Promise((resolve)=> {
editSession.on('changeAnnotation', () => {
const annotations = editSession.getAnnotations();
editSession.removeAllListeners('changeAnnotation');
editSession.destroy();
resolve(annotations);
});
});
};

Mocking functions in Jest

I am new to JavaScript testing and currently trying to write some test cases for a store (just an ES6 class) I created. I am using Jest as this is what we usually use for React projects, although here I am not testing a React Component but just a class wrapping a functionality.
The class I am testing extends another class, and has various methods defined in it. I want to test these methods (whether they are called or not), and also whether the properties declared in the class change as and when the corresponding class methods are called.
Now I have read about mocking functions, but from what I understand, they can only do checks like how many times a function is called, but can't replicate the functionality. But in my case, I need the functionality of the methods because I will be checking the class member values these methods change when called.
I am not sure if this is the right approach. Is it wrong to test functions in Jest without mocking? And inferentially, to test the internal workings of functions? When do we mock functions while testing?
The issue I am facing is that the project I am working on is a large one where there are multiple levels of dependencies of classes/functions, and it becomes difficult to test it through Jest as it will need to go through all of them. As I am using alias for file paths in the project, Jest throws errors if it doesn't find any module. I know its possible to use Webpack with Jest, but many of the dependent classes/functions in the code are not in React, and their alias file paths are not maintained by Webpack.
import { getData } from 'service/common/getData';
class Wrapper extends baseClass {
someVariable = false;
payload = null;
changeVariable() {
this.someVariable = true;
}
async getData() {
super.start();
response = await fetchData();
this.payload = response;
super.end();
}
}
This is a small representation of the actual code I have. Can't post the entire class here as I am working on a remote machine. Basically, I want to test whether changeVariable gets called when invoked, and whether it successfully changes someVariable to true when called; and similarly, check the value of payload after network request is complete. Note that fetchData is defined in some other file, but is critical to testing getData method. Also the path used here (service/common/getData) for importing getData is not the absolute path but an alias NOT defined in Webpack, but somewhere else. Jest can't resolve getData because of this. I will not have to worry about this if I mock getData, but then I will not be able to test its functionality I believe.
#maverick It's perfectly okay to test your class methods using jest. Check the code example in the link -
https://repl.it/repls/ClumsyCumbersomeAdware
index.js
class Wrapper {
constructor(){
this.someVariable = false;
}
changeVariable(){
this.someVariable = true;
}
getData(){
return new Promise(resolve => resolve('some data'));
}
}
module.exports = Wrapper;
index.test.js
const Wrapper = require('./index');
const wrapper = new Wrapper();
describe('Wrapper tests', () => {
it('should changeVariable', () => {
wrapper.changeVariable();
expect(wrapper.someVariable).toBe(true);
});
it('should get some data', () => {
wrapper.getData().then( res => expect(res).toBe('some data'));
});
});
This is a very simplistic example and in real life the async calls are much more complicated and dependent of 3rd party libraries or other project modules. In such cases it makes sense to have all the dependencies injected in out class and then mocked individually. For Example -
class GMapService {
constructor(placesApi, directionApi){
this.placesApi = placesApi;
this.directionApi = directionApi;
}
getPlaceDetails(){
this.placesApi.getDetails('NYC');
}
getDirections(){
this.directionApi.getDirections('A', 'B');
}
}
Now you can easily mock placesApi and directionApi, and test them individually without actually requiring Google Map dependencies.
Hope this helps ! 😇

NodeJS HTML rendering strategy

We want to build a production-ready social network website (Facebook or Instagram style). We plan to use Node on the server side and are looking for the best technology for rendering view components on the server side.
SEO friendliness is a must, so Angular doesn’t seem like a good fit (unless anyone can advocate for Prerender.io as a solid choice).
We also wish to support AJAX so that most of the rendering would be done on the server, but updates would be done on the client.
Having looked at React, it seems like a good choice, but there’s one thing I’m worried about - the fact that out of the box, you would need to load all data at the route level as opposed to the component level (since renderToString is synchronous - see the discussion here
I don't really understand what would be a robust alternative for server side rendering if we're passing on React.
If I understnd correctly, the basic way (which does allow async loading from within sub components) would be something like:
// The main page route
app.get('/',function(){
var html = renderBase();
renderHeader(html)
.then(
renderFeed(html)
).then(
renderFooter(html)
);
})
renderBase = function(){
return = "<html><body>..."
}
renderHeader = function(){
someIoFunction(function(){
// build HTML based on data
})
}
Seemingly using a template engine such as Jade might relieve us of some of the burden, but I can't seem to find anything on this "React vs. Templating engine" so-called issue, so probably I'm not seeing it correctly.
Thanks.
Basically the solutions come down to using a convention where each component has a static function which returns the data it needs, and it calls the function of the same name on the components it needs. This is roughly how it looks:
class CommentList {
static getData = (props) => {
return {
comments: api.getComments({page: props.page})
};
}
}
class CommentApp {
static getData = (props) => {
return {
user: api.getUser(),
stuff: CommentList.getData({page: props.commentPage})
};
}
render(){
return <CommentList comments={this.props.stuff.comments} />
}
}
Or you can figure out all of the data requirements at the top of the tree, but while simpler to start out, it's more fragile. This is what you do with templates.

React.js: modify render() method for all components?

For debugging reasons I'd like to add following line into general render() method, so it would be executed in all components.
console.log('render' + this.constructor.displayName, this.state);
I assume you want to do this without changing any of the existing code. I played around with this and found a way to do so if you're using something like webpack or browserify to build your application and you're using React v0.13.
It's important to note that this uses private methods, reaching into React's internals, and could break at any time. That said, it might be useful for your debugging purposes.
[Update to the Update]
If you use Babel, I highly recommend checking out the React Transform plugin. This will let you do all sorts of nifty stuff to React, including wrapping (or overwriting!) render methods.
[Update]
I've found a way to do this without hacking into React.addons.Perf; the key was the module name of ReactCompositeComponent and the function name of _renderValidatedComponent—just wrap that method to inject your custom behavior.
Note you'll need to place this code before you require("react").
var ReactCompositeComponent = require("react/lib/ReactCompositeComponent");
var oldRenderValidatedComponent = ReactCompositeComponent.Mixin._renderValidatedComponent;
ReactCompositeComponent.Mixin._renderValidatedComponent = function() {
var name = this.getName();
if (name && !name.match(/^ReactDOM/)) {
console.log("render: ", this.getName(), {props: this._instance.props, state: this._instance.state});
}
return oldRenderValidatedComponent.apply(this, arguments);
}
You'll then end up with a very similar result as the old answer, below. I've added better logging of props and state, and filter out any of the built in ReactDOM* components.
[Old Answer]
I've overridden the default measure function of the performance tools, which React calls through its codebase to measure performance when using React.addons.Perf. By doing so, we're able to get the information that the default measurement strategy would normally get. Note that this breaks the normal behavior React.addons.Perf.
Add this code to the entry-point of your application (after you require React):
var ReactInjection = require("react/lib/ReactInjection");
var ReactDefaultPerf = require("react/lib/ReactDefaultPerf");
ReactDefaultPerf.start();
ReactInjection.Perf.injectMeasure(function measure(moduleName, fnName, fn) {
return function() {
if (moduleName === 'ReactCompositeComponent' && fnName === '_renderValidatedComponent') {
var name = this.getName();
if (name) {
console.log("render: ", name);
}
}
return fn.apply(this, arguments);
}
});
And you'll get the following in your console logs:
ReactElements with no names (that is, components that make up regular HTML elements like span and div) are not shown. One notable set of exceptions is button and other input elements, as React provides composite components that wrap those to help manage state. They show up as ReactDOMButton and ReactDOMInput.
React supports Mixins for such cross-cutting concerns:
https://facebook.github.io/react/docs/reusable-components.html#mixins
However, it's not permitted to define a render method in a mixin. The restrictions on each of the React lifecycle methods are in the following source:
https://github.com/facebook/react/blob/0c6bee049efb63585fb88c995de788cefc18b789/src/core/ReactCompositeComponent.js#L189
If you could assign this behaviour to one of the other steps in the component lifecycle, mixins might work for you.

Categories