May i render the react-portal inside my react application? - reactjs

I use react 16. I want to render portal inside my app like that:
<html>
<body>
<div id="app-root">
<div>...My app stuff</div>
<div id="modal-root">... portal stuff</div> <-- portal content
</div>
</body>
</html>
But official doc recommends render portal next to, not in app.
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
Is it the only correct way to use the portal?

The idea of portal is that you can render it anywhere in the DOM tree, all you need is a valid DOM Node to render it into, its not necessary for it to be next to app-root
According to the Docs
However, sometimes it’s useful to insert a child into a different
location in the DOM:
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// domNode is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}

Related

How can I inject arbitrary string HTML content into the head of my gatsbyjs site?

I have a GatsbyJS site that I am working on where the main content source is a Wordpress install. One of the things I like to add to my sites is the ability to have placeholder areas in the site where I can control the content via the CMS. Usually I have a header_scripts area that goes at the bottom of the <head> tag, a body_scripts area that goes at the start of the <body> tag, and a footer_scripts area that goes at the bottom of the page <body>. With these three, I can usually integrate third-party add-ins pretty easily without having to do code deployments.
Sometimes I need to embed stylesheets, sometimes I need to embed script tags, and sometimes I need to throw in <meta> tags. Really the content could be anything. This data comes back as a raw string from my Wordpress GraphQL endpoint.
So now my question is, how do I get this content injected into my Gatsby site in the following places:
<html>
<head>
...
{header_scripts}
</head>
<body>
{body_scripts}
...
{footer_scripts}
</body>
</html>
I've found so far that I can just include the body_scripts and footer_scripts in a fairly regular manner in my Gatsby page template. In gatsby-node.js, I pass in the property values using the pageContext. It's kind of a bummer that they need to be wrapped in a <div /> tag, but they seem to work just fine.
import React from 'react'
export default class PageTemplate extends React.Component {
render = () => {
return (
<React.Fragment>
{this.props.pageContext.bodyScripts && (
<div dangerouslySetInnerHTML={{__html:this.props.pageContext.bodyScripts}} />
)}
{/* my page content here */}
{this.props.pageContext.footerScripts && (
<div dangerouslySetInnerHTML={{__html:this.props.pageContext.footerScripts}} />
)}
</React.Fragment>
)
}
}
Now for the real question. I am stumped on how to get the dynamic content from the header_scripts into the Gatsby server-side-rendering <head> tag. The closest thing I have found to being able to inject content into the head is to leverage the gatsby-ssr.js onRenderBody function. However, this seems to require pre-determined React component instances in order to function. I can't just pass it in plain raw string content and see the output in the page source:
export const onRenderBody = async ({
pathname,
setHeadComponents,
setHtmlAttributes,
setBodyAttributes,
setPreBodyComponents,
setPostBodyComponents,
setBodyProps
}, pluginOptions) => {
setHeadComponents(['<script>alert("hello");</script>'])
}
This results in an escaped string getting inserted into the <head> tag:
<html>
<head>
...
<script>alert("hello");</script>
</head>
<body>
...
</body>
</html>
I'm at a loss as to how to proceed. I can't just wrap my string in a <div /> tag like in the body because div tags can't go inside the head tag. I can't think of any head-capable HTML tags that would accept this kind of content.
The only idea I've had is to actually parse the string content into full React components. This seems daunting given the number of possible tags & formatting that I would need to support.
Am I going about this the wrong way? How can I get my arbitrary content into my Gatsby site's head tag?
It's a broad question and it will need some trials and errors to ensure that it's fully working without caveats in all scenarios but, among the things you've tried, you can add a few more options to the list to check which ones fit better.
Regarding the body_scripts and footer_scripts both can be inserted using the:
<div dangerouslySetInnerHTML={{__html:this.props.pageContext.footerScripts}} />
In any desired page or template. For the header_scripts and the meta tags (SEO), you can use the <Helmet> component. Basically, using this component, everything that is wrapped inside, it's becomes transpiled inside the <head> tag once compiled.
export default class PageTemplate extends React.Component {
render = () => {
return (
<React.Fragment>
<Helmet>
{this.props.pageContext.headerScripts && (
<div dangerouslySetInnerHTML={{__html:this.props.pageContext.headScripts}} />
)}
</Helmet>
{this.props.pageContext.bodyScripts && (
<div dangerouslySetInnerHTML={{__html:this.props.pageContext.bodyScripts}} />
)}
{/* my page content here */}
{this.props.pageContext.footerScripts && (
<div dangerouslySetInnerHTML={{__html:this.props.pageContext.footerScripts}} />
)}
</React.Fragment>
)
}
}
However, if the data comes from a CMS, it won't be available in the SSR yet, so, one easy thing you can do is to customize the outputted HTML (html.js) that Gatsby generates in each compilation. From the docs:
Customizing html.js is a workaround solution for when the use of the
appropriate APIs is not available in gatsby-ssr.js. Consider using
onRenderBody or onPreRenderHTML instead of the method above. As a
further consideration, customizing html.js is not supported within a
Gatsby Theme. Use the API methods mentioned instead.
Run:
cp .cache/default-html.js src/html.js
Or manually, copy the .cache/default-html.js file and paste it /src folder. There you can customize the final HTML.

React - load external js which outputs HTML into React Slick component

Just wondering what the procedure is loading an external script into a component which renders HTML?
I call a script, which returns me an unordered list which i want to use for an image carousel it looks something like this but currently isn't working
this is what i have inside my render
return(
<Slick {...settings}>
<script
type="text/javascript"
src="https://some.external.script.js"
/>
<div
class="render-html-here-from-script"
/>
<script>
SomeScript.require(['xxx'], function(arg)
{arg.loadAll().done(function(embed) {
// code here gets list and binds above in div
})}
)
</script>
</Slick>
)
the above code works fine if I just copy/paste straight into a standard HTML page
Thanks

Reactjs and localization

I'm currently in the process of learning React, so I've build my little game based on react components and I wanted to add translations for the various buttons needed.
The way we usually approach this is that the texts and translations are handled and edited by the client (as in the people we work for) on the server. Those translations would then be rendered right into the html template so that was it.
In React, however, everything is in js and component based. Does this mean that to make the move towards react we have to change our whole localization system, even on the server? Am I doing something wrong?
I tried having this
<script>
var translations = {
start: "Spiel starten"
}
</script>
<div id="App"></div> <!-- this is where I render React -->
but it looks like translations is undefined in my React App.js file
One option for handling this situation could be perhaps to pass in your translations object as a property on your component. For example:
<script>
var translations = {
start: "Spiel starten"
}
</script>
<div id="App"></div>
ReactDOM.render(
<Game translations={translations} />,
document.getElementById('App'));
As you have passed in your translations object into your react component, you should be able to use it in your template as such:
var Game = React.createClass({
render: function(){
return (
<div>
{this.props.translations.start}
</div>
);
}
});

AngularJS + UI Kit

I'm new to angularJS, When i use UIKit only its working fine (Yii2). When i use with angularJS the Javascript events like(tab, slider, dropdown, etc) not working.. I don't know what i have missed or is the any dependency i have to add it work this. This is my app.js code
var app = angular.module('nApp', [
'ui.router', //
'ngSanitize', // sanitize HTML
'ngAnimate', // CSS and JavaScript ng-animate
'ngRoute', // $routeProvider
'toaster', // toasterProvider
'mgcrea.ngStrap', // bs-navbar, data-match-route directives
]);
And my AppAsset.php file is like this,
public $css = [
'uikit/css/uikit.gradient.min.css',
];
public $js = [
'js/app.js',
'uikit/js/uikit.min.js',
];
public $depends = [
'yii\web\YiiAsset',
'frontend\assets\AngularAsset',
];
In AngularAsset.php I have loaded angular related files(css & js).
Please help me..
the reason you are having problems with integrating uikit components into your angularjs project is because uikit components like accordion or slider in your case, are instantiated the moment they are loaded. What this means is that if you place the slider.js file in the header of the first page you load, and your slider is in another html file not loaded yet, say users.html, then the slider.js file will not attach the component to your element in users.html.
If that doesn't clear things up, let me show you what I mean.
This is your index.html (the first html page you load).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!--loading uikit components in here will not work on other html files -->
</head>
<body data-ng-app="nApp">
<nav></nav>
<div class="container" data-ng-view>
<!--This is where the other html files will load in, like users.html -->
</div>
<footer></footer>
</body>
</html>
And this is users.html with an accordion component.
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.25.0/js/components/accordion.min.js"></script>
<!--This accordion will work-->
<div class="uk-accordion" data-uk-accordion="{collapse:false}">
<h3 class="uk-accordion-title">Title 1</h3>
<div class="uk-accordion-content">content one</div>
<h3 class="uk-accordion-title">Title 2</h3>
<div class="uk-accordion-content">content two</div>
<h3 class="uk-accordion-title">Title 3</h3>
<div class="uk-accordion-content">content three</div>
</div>
To elaborate, the accordion will work in users.html because the javascript file accordion.js is instantiated when users.html is loaded and so the component can attach to the respected element.
I hope this clears things up for you.
An other way is to say at UIkit, to make an 'watcher' on the dom
if you put data-uk-observe on the div.
Ui kit will be in place at this moment
Observe the DOM to auto-init new added components, e.g via AJAX.
If you inject dynamic HTML markup into the DOM via JavaScript, just add the data-uk-observe attribute to one of the parent elements to auto-initialize UIKit JavaScript components.
Usage
<div data-uk-observe>
<!-- inject your dynamic html here -->
</div>
Observe an element via JavaScript
UIkit.domObserve('#element', function(element) { /* apply on dom change within element */ })
Since AngularJS renders the HTML after the digest cycle, you have to re-render the UI component. Let's say for example for have the UIKit accordion:
<div id="myAccordion"
data-uk-accordion="{ showfirst: false }"
class="uk-accordion"
data-uk-observe>
Then, in the callback method from the controller for re-rendering the UI component you have to invoke:
UIkit.domObserve('#myAccordion', function (element) {
UIkit.component.boot('accordion');
UIkit.init(element);
});

Angularjs use same ng-app multiple times on page

I'm dealing with a content management system that needs to "inject" a reusable component into a page.
I want to inject the following component (html and javascript).
<script type="text/javascript">
if(typeof angular == 'undefined') {
document.write(unescape("%3Cscript type='text/javascript' src='/resources/scripts/lib/angular.min.js'%3E%3C/script%3E"));
document.write(unescape("%3Cscript type='text/javascript' src='/resources/scripts/pricing/app.js'%3E%3C/script%3E")); }
}
</script>
<div ng-app="pricing" ng-controller="PriceController as pc" ng-init="pc.getPrices('myprod', 'PER')">
Some text {{ pc.prices.msg["startdat tarief"] | jsDate }} .
More text {{ pc.prices.msg["einddat product"] | jsDate }}.
</div>
The component must be able to be injected multiple times on the page.
The problem is that the controller works fine, but only for the first injection.
This probably has something to do with that I am using the same app multiple times.
I am fairly new to angular.
How can I inject the same component multiple times?
Note that I am not able to init the app on a higher level. Because this would require the content manager to edit all pages, we juist want to inject a HTML component with javascript (i.e. the code snippet).
If you are able to add a unique ID to the module div you can manually bootstrap your angular app as follows:
function bootstrapAngular(id) {
angular.bootstrap(document.getElementById('module-' + id), ['app']);
}
angular.module('app', []).controller('sample', function ($scope) {
$scope.foo = 'bar';
});
bootstrapAngular(1);
bootstrapAngular(2);
bootstrapAngular(3);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div id="module-1">
<div ng-controller="sample">
{{ foo }}
</div>
</div>
<div id="module-2">
<div ng-controller="sample">
{{ foo }}
</div>
</div>
<div id="module-3">
<div ng-controller="sample">
{{ foo }}
</div>
</div>
You cannot have multiple ng-App directives on a single page. From the Angular.js documentation:
Only one AngularJS application can be auto-bootstrapped per HTML document. The first ngApp found in the document will be used to define the root element to auto-bootstrap as an application. To run multiple applications in an HTML document you must manually bootstrap them using angular.bootstrap instead. AngularJS applications cannot be nested within each other.
If placing ng-App higher in the tree isn't an option, you will have to re-write the components so that each component gets a unique angular.module() and when the component is injected, it will need to fire angular.bootstrap().

Resources