-1

Situation

I receive json from a cms that describes the content that needs to display on any given page. This project has 50+ components so rather than require all of them on every page I'd rather cherry pick them as needed.

Question

How can I

  1. Make sure all components are available for import (I assume this requires some webpack trickery)
  2. When converting the json's content node to jsx, making sure that any component described is rendered out.

Current Thoughts

I can loop through the raw jsx and collect all the tags for a given page then attempt a load for each tag via something like

const name = iteration.tagName;
dynCmps[name] = someAsynchronousLoad(path + name);

Then dispatch a redux event when loading is complete to kick off a fresh render of the page.

As for converting raw text content to react js I'm using ReactHtmlParser

best resources so far

Jacksonkr
  • 31,583
  • 39
  • 180
  • 284
  • are you saying json you get from cms is actually react component code or just content that needs to be rendered in react component? Also do you know usage of `React.lazy` if that meets your need? – Rikin Sep 06 '19 at 17:15
  • It's mostly the latter. CMS content is designed with a wysiwyg so the output would have components with attributes / styling. The more I dive into this to more I'm realizing the best move is simply to import every possible component the CMS wysiwyg at runtime even though only a few of them will be used on each page (this is where I might use something like `React.lazy`) – Jacksonkr Sep 06 '19 at 18:20
  • @Rikin I figured out workable solution - I plan to clean it up a bit more. Anyway, please take a look at let me know your thoughts. – Jacksonkr Sep 07 '19 at 17:51

1 Answers1

0

This had me stumped for a couple of days. After chatting with a colleague about it for some time it was decided that the amount of work it would take to offload the performance hit of loading all the components upfront is not work it for our scenario of 30-50 components.

Lazy loading CAN BE used but I decided against it as the extra 10ms of loading (if that) isn't going to be noticeable at all.

import SomeComponent from "./SomeComponent.js"

const spoofedComponents = {
    SomeComponent: <SomeComponent />
}

const replaceFunc = (attribs, children) => {
  const keys = Object.keys(spoofedComponents);
  for(var i in keys) {
    const key = keys[i];
    // lower case is important here because react converts everything to lower case during text-to-html conversion - only react components can be camel case whereas html is pascal case.
    if(attribs.name === key.toLowerCase()) { 
      return spoofedComponents[key];
    }
  }

  return <p>unknown component</p>
}

...

//inside render

const raw = "<SomeComponent><SomeComponent />"
// it's VERY important that you do NOT use self-closing tags otherwise your renders will be incomplete.
{parse(raw, {
    replace: replaceFunc
})}

In my case I have 30+ components imported and mapped to my spoofedComponents constant. It's a bit of a nuissance but this is necessary as react needs to know everything about a given situation so that the virtual dom can do what it is supposed to - save on display performance. The pros are that now a non-developer (editor) can build a layout using a WYSIWYG and have it display using components that a developer made.

Cheers!

Edit

I'm still stuck on adding customized props & children.

Edit

Basic props are working with

const spoofedComponents = {
  SomeComponent: (opts) => {
    let s = {};
    if(opts.attribs.style)
      s = JSON.parse(opts.attribs.style);
    if(opts.attribs.classname) {
      opts.attribs.className = opts.attribs.classname;
      delete opts.attribs.classname;
    }

    return <APIRequest {...opts.attribs} style={s}>{opts.children[0].data}</APIRequest>
  }
}

...

const replaceFunc = (opts) => {
  const keys = Object.keys(spoofedComponents);
  for(var i in keys) {
    const key = keys[i];
    if(opts.name === key.toLowerCase()) { 
      const cmp = spoofedComponents[key](opts);
      return cmp;
    }
  }

  return <p>unknown component</p>
}

Now to figure out how to add child components dynamically..

EDIT

This is working well enough that I'm going to leave it as is. Here is the updated replaceFunc

const replaceFunc = (obj) => {
  const keys = Object.keys(spoofedComponents);
  for(var i in keys) {
    const key = keys[i];
    if(obj.name === key.toLowerCase()) { 
      if(obj.attribs.style)
        obj.attribs.style = JSON.parse(obj.attribs.style);
      if(obj.attribs.classname) {
        obj.attribs.className = obj.attribs.classname;
        delete obj.attribs.classname;
      }

      return React.createElement(spoofedComponents[key], obj.attribs, obj.children[0].data)
    }
  }

  return obj; //<p>unknown component</p>
}
Jacksonkr
  • 31,583
  • 39
  • 180
  • 284