Manipulate dom element outside React component - javascript

I have a React component that renders HTML only on a small part of a HTML document.
From within the React component I need to replace an element, that exist outside the component, with a block of HTML.
No matter how much I'm googling this, I cannot find a straight way to accomplish this, I assume that it's because React's guidelines that naturally prescribe to use ref instead.
How would I use document.getElementById() or anything similar to insert the following sample HTML block at the place of a certain div:
<div>
<div class='yellow'>YELLOW</div>
<div class='green'>GREEN</div>
<div class='blue'>BLUE</div>
</div>

Assign an id to the top level element of the html react renders. It will still include the id attribute and thus can still be referenced with document.getElementById
<div id="toReplace">
<div className="yellow">YELLOW</div>
<div className="blue">blue</div>
<div className="red">red</div>
</div>
componentDidMount() { //or wherever
document.getElementById('toReplace').append('more html');
}

const str = "<div><div class='yellow'>YELLOW</div><div class='yellow'>GREEN</div><div class='yellow'>BLUE</div></div>"
document.getElementById('toReplace').innerHTML = "";
document.getElementById('toReplace').insertAdjacentHTML( 'beforeend', str );

Related

Angular ngOnDestroy didn't fire

Demo
Demo fixed accordingly to accepted answer
Consider a component, let's call it <simple-dialog>, with this template:
<button type="button" (click)="visible = !visible">TOGGLE</button>
<div *ngIf="visible">
<ng-content select="[main]"></ng-content>
</div>
I omit the component TypeScript definition cause it's basically the same as the one generated by ng-cli.
Then I use it like this:
<simple-dialog>
<div main>
<app-form></app-form>
</div>
</simple-dialog>
When i first click the button the child component is rendered; if I click the button again the child component is removed from the DOM.
The problem is that, at this point, app-form's ngOnDestroy is not called.
I'm new to angular, so I am not sure whether my expectation is wrong.
What you are trying to achieve is called conditional content projection.
In your case, by using ng-content, the components are instantiated by the outer component, unconditionally - the inner component just decides not to display it.
If you want conditional instantiation, you should pass a template instead:
<simple-dialog>
<ng-template>
<div main>
<app-form></app-form>
</div>
</ng-template>
</simple-dialog>
and use an #ContentChild annotated property to access
the template from the content within the SimpleDialogComponent:
#ContentChild(TemplateRef, { static: false })
content!: TemplateRef<unknown>;
which can then be rendered in the template as
<div *ngIf="visible">
<ng-container [ngTemplateOutlet]="content"></ng-container>
</div>
You can also read about this here:
https://angular.io/guide/content-projection#conditional-content-projection

get data attribute from “root” DOM node in react js

Let’s say there is a <div> somewhere in HTML file:
I want to pass an attribute data-person-name from that <div> having id root,to react element
<div id="root" data-person-name="John"></div>
const element = <h1>Hello,{data-person-name}</h1>;
ReactDOM.render(element, document.getElementById('root'));
Please suggest the right way to do that.
A cleaner way would be to prefix all custom attributes with "data-". Because there is no known attribute "personName" for a div element and having that is against W3 rules.
The code may look like this:
<div id="root" data-personName="John"></div>
const element = <h1>Hello,{document.getElementById('root').getAttribute('data-personName')}</h1>;

Rendering react habitat components through HTML thats passed through a prop

I am currently using react habitat on a project and i have components rendering through like the following
<div data-component="MyComponent" data-prop-titl="My title"></div>
I have encountered an issue where I am trying to pass through some markup from a rich text editor that contains markup like the example above.
I want to be able to render the rich text with the react-habitat components references inside.
So the reference above can basically be passed through with the rich text and still be rendered out as a react component.
Is this possible?
Probably something like this might help
function createMarkup() {
return {__html: '<div data-component="MyComponent" data-prop-titl="My title"></div>'};
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
But this is a very risky practice and can expose your users to a cross-site scripting (XSS) attack
I'm the original author of React Habitat.
You are going to run into issues putting HTML into an attribute string. Its the same problem with JSON
I would just make the HTML go inside the habitat component then use the proxy variable the React Habitat sets for you.
eg
<div data-component="MyWysiwygViewer">
<h2>Some HTML text</h2>
<p>With some body</p>
</div>
In the component i would get this data with:
constructor(props) {
super(props);
const html = props.proxy.innerHTML;
}
Alternatively
you could put it inside a hidden div
<div id="myHtml" style="display: none" >
<h2>Some HTML text</h2>
<p>With some body</p>
</div>
<div data-component="MyWysiwygViewer" data-prop-html-id="myHtml" />
Then in the component i would get this data with:
constructor(props) {
super(props);
const html = document.getElementById(props.htmlId).innerHTML;
}

Global JS variable to pass HTML chunk to React component

I have a piece of HTML I need to send to a React component on page load without rendering it. I'd rather not us AJAX due to cache, but I may revert to that if I can't figure this out.
On the jsp side, I have this:
<script>window.banner_data_for_desktop = "...droplet..."</script>
This contains the HTML chunk I need to pass
<div id="desktop-top-banner">
<div>
...
</div>
</div>
On the jsx side, I've tried rendering directly like this:
<div id="top_bar">{window.banner_data_for_desktop}</div>
This renders the content, but displays the div tags as a string and not output as HTML.
So then I tried using dangerouslySetInnerHTML like this:
<div id="top_bar">dangerouslySetInnerHTML={{ __html: window.banner_data_for_desktop }}</div>
This results in an error:
Invariant Violation: Objects are not valid as a React child (found: object with keys {__html}). If you meant to render a collection of children, use an array instead.
I've tried using Stringify, toString, creating a function to return the html like this:
function createMarkup() {
return {__html: window.banner_data_for_desktop};
}
All without any luck. If any one has a suggestion to render HTML from the global JS object, I would greatly appreciate it!
dangerouslySetInnerHtml should be an attribute of the tag:
<div dangerouslySetInnerHTML={{__html: "HTML CHUNK"}}></div>

How to remove extra wrapping elements in the rendered HTML?

I started learning angular 5 3 days ago so I'm quite new at it. I also use angularJS and React to develop applications and I think I don't understand how angular 5 components fully work. If I create for example a custom button that has a custom text inside (I'm not saying this should be done this way but it's a simple example that shows my point) like this:
<app-button>
<app-text>
My Text
</app-text>
</app-button>
The rendered DOM results in:
<app-button>
<button>
<app-text>
<span>
My Text
</span>
</app-text>
</button>
</app-button>
which is unreadable, I wanted to know if there's a way to remove this wrapping elements and just place the components layout replacing the tags resulting in the following structure:
<button>
<span>
My Text
</span>
</button>
If there's no way of removing them what are your suggestions? thanks!
Angular components are directives with templates. According to this:
Directive configuration #Directive({ property1: value1, ... })
selector: '.cool-button:not(a)' Specifies a CSS selector that
identifies this directive within a template. Supported selectors
include element, [attribute], .class, and :not().
So component selectors can be also attribute selectors. For your example, instead of writing this:
parent.component.html:
<app-button>
<app-text>
My Text
</app-text>
</app-button>
write this:
parent.component.html:
<button app-button>
<span app-text>My Text</span>
</button>
where :
app-button.component.ts
...
selector: '[app-button]',
template: `<ng-content></ng-content>
...
app-text.component.ts
...
selector: '[app-text]',
template: `<ng-content></ng-content>`
...
this would be rendered as you expected:
Update after your comment about styling those buttons:
To style the buttons from inside the button component, and set class in parent component, use :host-context pseudo-class. It is not deprecated and works well
button.component.css
:host-context(.button-1) {
background: red;
}
:host-context(.button-2) {
background: blue;
}
app.component.html
<button app-button class="button-1">
<span app-text>My Text</span>
</button>
<button app-button class="button-2">
<span app-text>My Text</span>
</button>
Here is the DEMO
I had a similar issue. I'll provide my solution in case someone else has the same problem.
My component should be able to be used either within other components or as a route from <router-outlet></router-outlet>. When I used the selector as an attribute [my-component] things worked perfectly provided it was used within other components. But when created by <router-outlet></router-outlet> a <div> were created automatically.
To avoid that, we can simply use multiple selectors, and consider that the selectors can be combined.
Consider this: I want my component to use the attribute my-component and if it ever should be created by the <router-outlet></router-outlet> it should be wrapped in a <section></section>. To achieve this simply use:
#Component(
selector: 'section[my-component], my-component',
...
)
The result will be, if used inside another tag:
<whatevertag my-component>
... component content ...
</whatertag>
If used as a route:
<section my-component>
... component content ...
</section>

Categories