Aécio Cavalcanti
Aécio Cavalcanti

Reputation: 11

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>
<a href="some url">some description</a>

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:

  1. Is there any way I can make my first two approaches work?
  2. 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?
  3. 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?

Upvotes: 1

Views: 702

Answers (1)

Adam
Adam

Reputation: 3965

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.

Upvotes: 1

Related Questions