How to dynamically add scripts received from CMS to <Head> - javascript

I have an endpoint which returns an array of scripts. These scripts can either be with a src i.e. <script src="https://www.google-analytics.com/analytics.js"><script> attribute OR they may also have body content i.e. something like; <script>(function(// do something to generate scripts))<script> which will create new scripts on the fly.
I'm currently using getServerSideProps to get the data from each individual page and then in _app.js I am getting that data from pageProps. The data is landing in fine, however I was hoping to simply return the script code in <Head> but obviously React escapes any HTML that isn't set using dangerouslySetInnerHTML so that makes this task somewhat more difficult.
I have looked through the documentation and see no feasible way to just simply put the script in string form in the <Head> tag. This would be the simplest solution.
Here's my idea;
I would check if the script has a src, if it doesn't we can assume that it has body content and thus will create new scripts on the fly.
If the script has an src, we extract it and return a JSX script tag and populate the src with what we got back from the CMS.
If the script has no src tag, we remove all script tags and then use dangerouslySetInnerHTML to set the contents of the script to a JSX script instance.
Is there an another way to do this, with fewer steps? It feels like there should be.

You can use html-to-react. Here is an example:
import Head from "next/head";
import { Parser } from "html-to-react";
const IndexPage = () => {
const { parse } = new Parser();
const scriptString = "<script>console.log('hello')</script>";
return (
<>
<Head>{parse(scriptString)}</Head>
<div>Hello World.</div>
</>
);
};
export default IndexPage;

Related

adding a script on Gatsby with setPostBodyComponents only on certain pages

Is it possible to render a script that has been inserted within the body tag (using setPostBodyComponents) only in certain pages other than all of them ?
Any ideas if this would be possible ?
As you can see in gatsby-ssr-js docs, onRenderBody exposes a bunch of props where there's a pathname.
pathname {string}
The pathname of the page currently being rendered.
That said, you can try something like:
const React = require("react")
exports.onRenderBody = ({ setPostBodyComponents, pathname }) => {
const SCRIPT_PAGES = ['/page-1', '/page-2'];
if(SCRIPT_PAGES.includes(pathname)){
setPostBodyComponents([
<script dangerouslySetInnerHTML={{ __html:`your script here`}} />,
]);
}
};
In that case, your SCRIPT_PAGES will hold the pages where the script will be inserted. Using that approach, you can tweak it as you want.

Preact isn't replacing dom elements on rehydration?

I'm trying to learn rehydration of dom using preact. For some unknown reason, the render function isn't replacing the original DOM node but rather appending to it.
https://github.com/preactjs/preact/issues/24, The 3rd parameter of render should afford the opportunity to replace:
render(<App />, into, into.lastChild);
https://codesandbox.io/s/beautiful-leavitt-rkwlw?file=/index.html:0-1842
Question: Any ideas on how I can ensure that hydration works as one would expect it to e.g. replace the static counter with the interactive one?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test</title>
</head>
<body>
<script>
window.__STATE__ = { components: {} };
</script>
<main>
<div>
<script data-cmp-id="1">
window.__STATE__.components[1] = {
name: "Counter",
props: { id: 1 }
};
</script>
<div>HOW MANY LIKES 0</div>
<button>Increment</button>
</div>
</main>
<script type="module">
import {
html,
useState,
render
} from "https://unpkg.com/htm/preact/standalone.module.js";
let id = 0;
export const withHydration = Component => props => {
id += 1;
return html`
<${Component} ...${props} />
`;
};
const Counter = () => {
const [likes, setLikes] = useState(0);
const handleClick = e => {
e.preventDefault();
setLikes(likes + 1);
};
return html`
<div>HOW MANY LIKES ${likes}</div>
<button onClick=${handleClick}>Increment</button>
`;
};
const componentMap = {
Counter: withHydration(Counter)
};
const $componentMarkers = document.querySelectorAll(`[data-cmp-id]`);
Array.from($componentMarkers).forEach($marker => {
debugger;
const $component = $marker.nextElementSibling;
const { name, props } = window.__STATE__.components[
$marker.dataset.cmpId
];
const Component = componentMap[name];
render(
html`
<${Component} ...${props} />
`,
$component.parentNode,
$component
);
});
</script>
</body>
</html>
All of this is inspired by https://github.com/maoberlehner/eleventy-preact repo.
There are two things going on here, I'll explain each:
1. The third argument to render() should not be needed here.
Your Counter component has two elements at the root (<div> and <button>), and passing a single DOM element reference as the third argument to render is going to prevent Preact from using the <button> that exists in the "prerendered" DOM.
By default, render(vdom, parent) will look at all of the children of parent and figure out which ones should be re-used when attaching to existing DOM. There is only one very specific case where this behavior doesn't work and the third argument is warranted, which is when multiple "render roots" share the same parentNode. In general that's a case best avoided, which is why that third parameter isn't really advertised much in documentation.
2. `htm/preact/standalone` currently appears to be broken
I'd come across a similar issue last week, so I knew to check this. For some reason, when we bundled Preact into HTM to create the standalone build, it broke rendering. It's likely a result of overly aggressive minification, and should be fixed soon.
In the meantime, it's possible (and sometimes better) to use htm + preact + preact/hooks directly from unpkg. The key is to use the fully resolved module URLs, so that unpkg's ?module parameter is converting imports to the same URLs you've used for your manual ones. Here's the correct URLs for your demo:
import htm from "https://unpkg.com/htm#latest?module";
import { h, render } from "https://unpkg.com/preact#latest?module";
import { useState } from "https://unpkg.com/preact#latest/hooks/dist/hooks.module.js?module";
With the third render argument removed and those imports swapped out, your demo actually works fine: 👍
https://codesandbox.io/s/fast-fire-dyzhg?file=/index.html:719-954
Bonus Round: Hydration
My head is very much in the hydration space right now, so this is of great interest to me. There's a couple of things I would recommend changing in your approach based on my research:
1. Use JSON for data instead of script tags
Inline scripts block rendering and force all stylesheets to be loaded fully prior to executing. This makes them disproportionately expensive and worth avoiding at all costs. Thankfully, the solution is pretty simple: instead of using <script>__STATE__[1]={..}</script> for your component hydration data/callsites, switch to <script type=".."> with a non-JavaScript mimetype. That will make the script non-blocking, and you can quickly and easily parse the data as JSON when you hydrate - far faster than evaluating JS, and you control when it happens. Here's what that looks like:
<div data-component="Counter">
<div>HOW MANY LIKES 0</div>
<button>Increment</button>
<script type="text/hydration-data">
{"props":{"id":1}}
</script>
</div>
Notice that you can now use the location of that script tag inside your data-component root to associate it with the component without the need for global IDs.
Here's a fork of the fixed version of your demo, with the above change:
https://codesandbox.io/s/quirky-wildflower-29944?file=/index.html:202-409
I hope you find the updated root/data/render loop to be an improvement.
2. Use hydrate() to skip diffing
If you know that your pre-rendered HTML structure exactly matches the initial DOM tree structure your components are going to "boot up" into, hydrate() lets you bypass all diffing, boot up quickly and without touching the DOM. Here's the updated demo with render() swapped out for hydrate() - no functional difference, just it'll have better performance:
https://codesandbox.io/s/thirsty-black-2uci3?file=/index.html:1692-1709

Send React component's HTML to server

So I have a react component, <ReviewScreen />. It takes in some props, styles them, and presents them. I need to convert this component into HTML, and send it to my backend so that I can generate a PDF on my server.
I tried:
import { renderToStaticMarkup } from 'react-dom/server';
import ReviewScreen from '../somePath';
const html = renderToStaticMarkup(ReviewScreen)
// ... send the html variable to backend using some HTTP library
Problem: the html variable is always empty. My question is: a) is this a 'good' way of doing what I'm trying to achieve? b) is there a better method?
UPDATE: Changing to <ReviewScreen /> actually gets a response. However, I forgot to mention that this component also talks to my Redux store which results in an Invariant Violation: Could not find “store” error.
renderToStaticMarkup takes in an element, not the component type - you'd need to do this:
const html = renderToStaticMarkup(<ReviewScreen />)
Or, without JSX:
const html = renderToStaticMarkup(React.createElement(ReviewScreen))
That said, I think you would be better off generating the HTML on the server instead - rendering arbitary markup sent from the client seems like it could get messy.
You should be doing this:
const html = renderToStaticMarkup(<ReviewScreen />)
Not:
const html = renderToStaticMarkup(ReviewScreen)

Is it possible to parse HTML together with its style components in a css file, into jsPDF `doc.fromHTML()`? Using React for my HTML components

Here is my current code using Reactjs which results in the rendering of the above HTML components in my webpage.
I want the block shown in the image to appear in my PDF using jsPDF doc.fromHTML() method.
import React from 'react';
const Pdf = () => {
return (
<div className="grid-container">
Some words
</div>
);
}
export default Pdf;
Here are codes for jsPDF.
const htmlstring = renderToString(<Pdf />);
let doc = new jsPDF();
console.log(htmlstring);
doc.fromHTML(htmlstring);
doc.save('test.pdf');
however, this code results in a completely blank .pdf file.
Is it possible to use renderToString() as I did above to get a HTML string together with its styling? Is there any available way to obtain a HTML string that can be passed into doc.fromHTML() such that the output PDF will have all the styling included in the HTML string?

Draft.js and stateToHTML, how to render output html in react component?

I've got my Draft js editor working fine, it saves to my database, using convertToRaw(editorState1.getCurrentContent()), and I am getting it back and converting it to HTML using draft-js-export-html and stateToHTML(convertFromRaw(dbContent.content.text01)).
So far so good... but now I have raw HTML that I want to display in my react component, and here comes the trouble.
Just rendering it with {props.text01} outputs it as a string and turns into <p>adfasfadfs</p> as text.
Obviously I want it to render as HTML. How do I do that?
I have looked at dangerouslySetInnerHTML but I would prefer not having ANOTHER plugin just to get my Draft js working the way I want it to.
Am I going at it the wrong way, or is dangerouslySetInnerHTML the only way to do it?
As described in this answer, React will escape HTML by default (that's why it's just rendered as a string). But you can force the rendering by using the dangerouslySetInnerHTML property. You'd just have to take potential XSS attacks into consideration.
If Draft is already installed, another way to just simply render the content is to use the readOnly prop on the DraftEditor.
you can use this npm module.
link is link
npm install --save html-to-react
Examples
Simple
The following example parses each node and its attributes and returns a tree of React elements.
var ReactDOMServer = require('react-dom/server');
var HtmlToReactParser = require('html-to-react').Parser;
var htmlInput = '<div><h1>Title</h1><p>A paragraph</p></div>';
var htmlToReactParser = new HtmlToReactParser();
var reactElement = htmlToReactParser.parse(htmlInput);
var reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement);
assert.equal(reactHtml, htmlInput); // true

Categories

Resources