How to open some child links in new tab in React - javascript

I have a page built with React (NextJS) and I am pulling some markup string content from Wordpress and inserting it into my JSX, like so:
...
<div className="wrapper">
<p
className="text-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
></p>
</div>
...
Now, the markup possibly contains links and I want to open all those links on new tab. So I tried:
...
<div className="wrapper">
<base target="_blank" />
<p
className="text-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
></p>
</div>
...
and all links in the markup are opened on new tab so, great. But the problem is that all other links in the page including those outside the div.wrapper element are opened in new tabs (since <base /> is scoped to the entire page) and I'll like to prevent this.
Since I can't use multiple <base /> on the same page, the other option I'm aware of is to loop through anchor tags of interest with document.querySelector(".wrapper a") and add the target attribute to all of them but, in React it's an anti-pattern to modify the DOM directly.
So I'm not sure how best to proceed. What do I do?

You can use DOMParser API to achieve that.
Here's a little snippet
const parser = new DOMParser();
const htmlText = `<div>Url link</div>`;
let content = parser.parseFromString(htmlText, "text/html");
const anchors = content.getElementsByTagName('a');
Array.from(anchors).forEach(a => {
a.setAttribute("target", "_blank");
})
console.log(content.body.innerHTML); // Here it is your new string
The code may need to be improved a bit, I've just typed this out of MDN example and I didn't have time to test it. Does this work?

Well first of all base element should only be inserted in html head element, and not inside the body html element, you could do that imperatively or using the react-helmet library - if you still need to use it.
dangerouslySetInnerHTML is in itself an imperative pice of code, but sometimes its the only possible solutions for a certain use cases, now regarding the links you could either do it using imperative code in a useEffect or componentDidMount, or you code use react-html-parser which will enable you to modify dom elements in a more declarative fashion - i say in a more declarative fashion because while its a react component in practice its still more imperative than its declarative in nature, but still better than custom code running in useEffect or componentDidMount

Related

What's the best way to pass markdown to an Astro component as a prop

What I'm trying to do
A simple way to render the content of a markdown file when it's passed as a string to another component using .compiledContent (or even using .rawContnent)? Or even a better way than this as obviously usually in Astro we can use the <Content /> Component, but from my knowledge, I can't pass a component or this functionality to another component without using a <slot /> in the parent component.
I have some JS for the parent component and using a <slot/> instead of passing the props to the component would change things, so hopefully looking for a solution with using this.
My setup
Data stored in /src/data/experience as markdown files with a year and a description formatted as markdown in the content section of each file
A component called Tabs.astro which takes props of headings and contents which are both lists of strings
A page /src/pages/experience.astro with the Tabs component in it which is displaying this data
I take the below code to get the data from the markdown files and pass the years and descriptions to the Tab component.
experience.astro
---
import Tabs from "../components/Tabs.astro";
const jobs = await Astro.glob("../data/experience/*.md");
const years = jobs.map((job) => job.frontmatter.year);
const descriptions = jobs.map((job) => job.compiledContent);
---
<!-- My component taking the data to be rendered -->
<Tabs headings={years} contents={descriptions} />
Tabs.astro
And the component renders the info like so
<!-- Tabs -->
<div class="tabs">
<ul class="tabs-header">
{
headings.map((heading) => (
<li>{heading}</li>
))
}
</ul>
<ul class="tabs-content">
{contents.map((content) => <li class="tab">{content}</li>)}
</ul>
</div>
My current solution
At the moment using .compiledContent gets me the correct HTML, however it is all in a string so the HTML doesn't actually render.
What I'm looking for
Is there a native way in Astro to pass markdown as a prop to a component?
If not is there a manual and recommended way in Astro to convert a markdown string and sanitise it to protect against XSS attacks? (if this is a risk in Astro when rendered statically?)
If not what are your most recommended ways to render markdown and sanitise it in JS?
Thanks so much for your time and help! I'm loving using Astro
p.s Also happy to concede and just use a <slot/> in my component if needed... ;)
Astro has a set:html directive you can use in combination with a Fragment like this
<Fragment set:html={post.compiledContent()}/>
After a bit of struggling with this myself, the current solution from the Astro docs for a single file without looping is the following.
Import your file with {Content as YourAliasName} from '../yourPath/yourFileName.md'
Then just use it as a tag <YourAliasName />
Example from the docs for reference:
---
import {Content as PromoBanner} from '../components/promoBanner.md';
---
<PromoBanner />
https://docs.astro.build/en/guides/markdown-content/#the-content-component

Can not add code comment inside GatsbyJS function

How I add code comment inside this Gatsby function. I search in web but not found yet.
Try:
export default () => {
// your comment here
return <div>
<NavBar />
{/* another comment here*/}
Hello People
<hr />
<Link to="/blog/">Blog Page</Link>
<div>
}
Note that I've self-closed the empty tags.
Adding the comment of Oliver:
To be precise, the syntax of HTML inside JS is called JSX, and it's a
syntactic extension to JavaScript typically employed by React (and
thus GatsbyJS) for cleaner code. To embed a JS expression in JSX you
need to use curly braces like this: {some expression}. Note that the
expression can be a comment, hence the {/*comment*/}
So The question was "How to add comment inside Gatsby JS function" That was a Tricky question. Actually I want to comment inside div elements. I found the solution. So t want to answer on my own.
Comment inside function but outside div element's #ferran-buireu is right but inside div element code comment be like;
{/* code comment */}

React: How to use same component twice in same page but loading one script tag for both just one time

I created a react component that I want to use twice(or more) inside my page, and I need to load a script tag for it inside the head of my page but just once! I mean even if I use the component twice or more in the page it should add the script tag just once in the head.
The Problem is that this script tag should be absolutely a part of the component and not statically inserted in the head of my page.
Can anyone help me to make the magic happens? Thanks a lot in advance!
You can give react-helmet a try for managing changes to your <head> from within React components.
In particular, you can check this example where rendering the same element four times only adds the script tag once.
For completeness, the relevant code from the example (although the interesting part is to see how it executes):
import { Helmet } from "react-helmet";
function ComponentWithHeader() {
return (
<div>
<div>Oh hi</div>
<Helmet>
<script src="fake-url.js" />
</Helmet>
</div>
);
}
const App = () => (
<div>
<ComponentWithHeader />
<ComponentWithHeader />
<ComponentWithHeader />
<ComponentWithHeader />
</div>
);
You can set the state of the parent component to keep in memory that the script is already added.
if (!this.state.scriptAdded) {
// Add script tag
this.setState({ scriptAdded: true });
}

What is an elegant way to initialize multiple react-live components from a static website?

I'm currently creating a react component library that i want to provide as an npm package. I also want to provide a documentation that features fancy live rendering of the available components on e.g. github pages.
For the live editing feature i'm planning to use react-live which provides multiple react components to display a live editor and a preview. See an example from the styled-components docs how this looks like.
The react-live component accepts a string code containing the initial code that should be displayed in the editor and a list of components scope that can be used inside the live editor.
Now i want to use gohugo or a similar static site generator to deploy my documentation. I thought i could maybe provide a <div> inside my static site that has a special class react-live-demo and i will get these containers with document.getElementsByClassName('react-live-demo'), loop over them and render the react-live component into it.
I created a code snippet that shows a little example:
const {LiveProvider, LiveEditor, LiveError, LivePreview} = window['react-live'];
// a random component that i want to render in the live editor
const MyComponent = () => (
<div>
<h1>react live demo</h1>
<p>This is a component from the script tag.</p>
</div>
);
// a wrapper for the react-live editor
const Editor = ({code, scope}) => (
<LiveProvider code={code} scope={scope}>
<div className="live-example row">
<div className="col-xs">
<LiveEditor />
<LiveError />
</div>
<div className="col-xs">
<LivePreview />
</div>
</div>
</LiveProvider>
);
// get all containers that have the initial code as innerHTML
const examples = document.querySelectorAll('script[data-name="react-live"]');
examples.forEach(example => {
// insert a new div before the script tag
const container = document.createElement('div');
example.parentNode.insertBefore(container, example.nextSibling);
// render the editor with the code from the script tag
const code = example.innerHTML.trim();
ReactDOM.render(<Editor code={code} scope={{MyComponent}} />, container);
});
.static-content {
background-color: papayawhip;
}
.live-example {
border: 1px solid orange;
}
.react-live-demo.code {
display: none;
}
.invalid {
color: red;
}
<p class="static-content">HERE IS SOME STATIC HTML CONTENT</p>
<script type="text/html" data-name="react-live">
<div>
<h1>react live demo</h1>
<p>This code is editable.</p>
</div>
</script>
<p class="static-content">SOME MORE STATIC HTML CONTENT</p>
<script type="text/html" data-name="react-live">
<MyComponent />
</script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/react-live#1.7.0/dist/react-live.min.js"></script>
The question is now:
How do embed the code string into my static site so that i can grab it with my js code and pass it to my react-live component?
I thought about <pre> or something like that. Or should i just embed it as inner html and read that? But then it would be displayed for short when the react component hasn't rendered yet. Or should i use some sort of script tag and make it available as globals? The key goal is to make it as easy as possible to add live editing examples to the documentation without touching any javascript code.
However if i pass something like <MyComponent /> as innerHTML this does of course not work as you can see in the code snippet.
Any best practice for that use case appreciated.
EDIT:
Based on the suggestion of #Daniel Alexandrov i edited the code snippet. The solution now reads the innerHTML of script tags with type="text/html set and creates a <div> container to insert the editor. This seems to work quite well. Any more ideas?
In my opinion the best choice is to use the <script> tag with a custom type attribute. Take a look at the Knockout.js template binding which uses type="text/html or the Angular <script type="text/ng-template">
This way the browser will ignore the script tag completely, because it doesn't know how to interpret it.

trying to add br tag between the text in react

I am trying to add br tag between the text in react.
can you guys tell me how to add it.
I tried adding br tag... but if I add I get tag in the browser.
providng my code below.
can you guys tell me how to fix it.
let sportsDescription = '';
if(sportsInfo !== '' && !isSportsSetupActive) {
sportsDescription = 'testing <br /> testing';
}else if(isSportsSetupActive) {
sportsDescription = 'testing <br /> testing';
}
return (
<div id="main" className="section flex-container flex-full">
<section className="page-content flex-item">
<section className="gray-box">
<h2>Welcome to your dashboard{formatName}.</h2>
<p className="ft-intro ft-day1 ft-day2">{sportsDescription}</p>
</section>
</section>
</div>
);
From the ReactJS Docs...
In general, setting HTML from code is risky because it's easy to inadvertently expose your users to a cross-site scripting (XSS) attack. So, you can set HTML directly from React, but you have to type out dangerouslySetInnerHTML and pass an object with a __html key, to remind yourself that it's dangerous.
In your example, {sportsDescription} is interpreted as a string. React won't see anything inside that variable as a DOM element. That's why your <br/> only renders as a string.
Alternatively, you could do this (but it's generally a bad idea)...
<p
className="ft-intro ft-day1 ft-day2"
dangerouslySetInnerHTML={{__html:sportsDescription}}
/>
This way, React correctly recognized your intent to include HTML inside the string sportsDescription and renders the desired <br/> element.
Here's a quote from an article that goes into greater detail about Getting Started with ReactJS:
The React module has a createClass method that takes an object as its only parameter. We define only one key to start with: render. The value of render is a function that returns a virtual DOM element. One of the keys to React’s performance is its virtual DOM abstraction. Essentially, it’s a difference engine that calculates the difference between the existing DOM and the DOM that is to be rendered and only modifies the necessary elements and attributes. Inside the render function, you may have noticed that I included HTML markup. That’s JSX. It won’t pass validation as legal Javascript. Instead, it requires the Babel transpiler.
JSX only looks like HTML. JSX is just a syntactic extension of Javascript, where every JSX "tag" is actually a call of React.createElement function. SO you can't treat JSX as HTML, and therefore you can't simply use strings with HTML inside and expect them to act like DOM. What you should do is return other valid JSX:
let sportsDescription = '';
if(sportsInfo !== '' && !isSportsSetupActive) {
sportsDescription = (
<span>testing<br />testing</span>
);
} else if(isSportsSetupActive) {
sportsDescription = (
<span>testing<br />testing</span>
);
}
return (
<div id="main" className="section flex-container flex-full">
<section className="page-content flex-item">
<section className="gray-box">
<h2>Welcome to your dashboard{formatName}.</h2>
<p className="ft-intro ft-day1 ft-day2">{sportsDescription}</p>
</section>
</section>
</div>
);
Here,
<span>testing<br />testing</span>
is actually four calls of React.createElement, the first one is for span element, and three other are for its children: text node, br, text node.
Please read the documentation carefully.

Categories

Resources