How to use <Link /> component inside dangerouslySetInnerHTML - javascript

Currently I have this in one of my components:
{someObject.map(obj => (
<div
dangerouslySetInnerHTML={{
__html: obj.text
}}
/>
))}
Basically, I am mapping over someObject which on another file. The structure is like this:
export default someObject = [
{
obj: "<p>Some text 1.</p>"
},
{
obj: "<p>Some text 2.</p>"
}
]
I'm just simplifying the content for demonstration's sake. However, I ran into a problem because I need to use the <Link /> component in one of the items. As in:
export default someObject = [
{
obj: "<p>Some text 1.</p>"
},
{
obj: "<p>Some text 2.</p>"
},
{
obj: "<p>Some text 2 and <Link to="/someroute">link</Link>.</p>"
}
]
However, it's not working because that entire <p></p> tag is wrapped in dangerouslySetInnerHTML.
I can just use plain <a></a> tag for the link but that doesn't seem like a good solution as the entire application would reload instead of just going to another route.
What are the other options to make this work?

Why don't you just export the object as a jsx object? I think use dangerouslySetInnerHTML is a bad practice, it might cause XSS attack.
const someObject = [
{
obj: <p>Some text 1.</p>
},
{
obj: <p>Some text 2.google</p>
}
]
class App extends React.Component {
render(){
return (
<div className="App">
<h1>Hello world</h1>
<h2>Jsx object goes here {someObject[1].obj}</h2>
</div>
)};
}
const rootElement = document.getElementById("container");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

There's is two ways to solve this problem :
First Way :
it's like a more general approach you can use it to opt your code.
try to use this library (https://github.com/tasti/react-linkify/)
Here's the simpler form of the component :
import React, {PropTypes} from 'react';
import Linkify from 'react-linkify';
export default class TextWithLink extends React.Component {
constructor(props) {
super(props);
}
render() {
let text = this.props.text;
if(this.props.showLink) {
text = <Linkify properties={{target: '_blank', rel: "nofollow noopener"}}>{text}</Linkify>
}
return (<div>{text}</div>);
}
}
Second Way :
In case, if you want to create a hyperlink (<a>), you should have a function which builds elements and returns the result.
Example :
list = {
text: 'hello world',
link: 'www.facebook.com'
}
And the render function could be something like :
buildLink() {
return(
<p>
{list.text}. <a href={list.link}>{list.link}</a>
</p>
);
}
render() {
return (this.buildLink());
}

export default someObject = [
{
obj: "<p>Some text 1.</p>"
},
{
obj: "<p>Some text 2.</p>"
},
{
obj: linkto('/someroute')
}
]
linkto will solve your issue.

Related

How to use images in the array in the react?

I am creating a website. I am a beginner. I have an issue. I have an array of react components. I don’t know can I use React components as the array elements. They are images, imported from the folder of my project. Also, I have an array of names of news companies. The idea is to create blocks with the name and image above. I want to create blocks according to the my images array length. So if the length of this array is 4, the cards I have 4. The issue is I can't display images, I imported them to my project. Main code is in the main page component. Also, I have a component called Author Card. In it, I have a React component, that receives name and image as the props and put them in the card Html block.
Here is my main page component code:
import React from 'react';
import AuthorCard from "./MainPageComponents/AuthorCard";
import BBC_Logo from '../assets/images/BBC_Logo.png';
import FOX_Logo from '../assets/images/FOX_Logo.png';
import CNN_Logo from '../assets/images/CNN_logo.png';
import ForbesLogo from '../assets/images/forbes-logo.png';
function MainPage(props) {
const channels = [
{
name: 'BBC',
index: 1
},
{
name: 'FOX',
index: 2
},
{
name: 'CNN',
index: 3
},
{
name: 'FORBES',
index: 4
},
];
const logos = [
<BBC_Logo key={1} />,
<FOX_Logo key={2}/>,
<CNN_Logo key={3}/>,
<ForbesLogo key={4}/>
];
return (
<div className="main-page">
<div className="main-page_container">
<section className="main-page_channels">
{channels.map( (channel) => {
logos.map( (logo) => {
return <AuthorCard name={channel.name} img={logo} />
})
})}
</section>
</div>
</div>
);
}
export default MainPage;
Here is my Author Card component code:
import React from 'react';
function AuthorCard(props) {
return (
<div className="author-card">
<div className="author-img">
{props.img}
</div>
<div className="author-name">
{props.name}
</div>
</div>
);
}
export default AuthorCard;
Please, help!
I would handle this a bit differently. First thing the way you import your logos is not imported as a component. Rather you get the path/src of the image which you can then use in a component. Read more about that here: https://create-react-app.dev/docs/adding-images-fonts-and-files/
So the way I would do this is to put the logo img src into your channels array and then pass that img src to the AuthorCard component. Then in the AuthorCard component your use a component to render the image. Like this:
import React from "react";
import BBC_Logo from "../assets/images/BBC_Logo.png";
import FOX_Logo from "../assets/images/FOX_Logo.png";
import CNN_Logo from "../assets/images/CNN_logo.png";
import ForbesLogo from "../assets/images/forbes-logo.png";
export default function App() {
return (
<div className="App">
<MainPage />
</div>
);
}
const channels = [
{
name: "BBC",
index: 1,
img: BBC_Logo
},
{
name: "FOX",
index: 2,
img: FOX_Logo
},
{
name: "CNN",
index: 3,
img: CNN_Logo
},
{
name: "FORBES",
index: 4,
img: ForbesLogo
}
];
function MainPage(props) {
return (
<div className="main-page">
<div className="main-page_container">
<section className="main-page_channels">
{channels.map((channel) => {
return <AuthorCard name={channel.name} img={channel.img} />;
})}
</section>
</div>
</div>
);
}
function AuthorCard(props) {
return (
<div className="author-card">
<div className="author-img">
<img src={props.img} alt="author card" />
</div>
<div className="author-name">{props.name}</div>
</div>
);
}
Here, we are using the map function to iterate over the channels array and render an AuthorCard component for each channel. We pass the name property to the AuthorCard component, as well as the corresponding logo from the logos array.
Note that we are also passing a key prop to the AuthorCard component to help React identify each component uniquely. In this case, we're using the index property of each channel object.

How to access the value inside array of object within that array

Update:
Basically i want the same output but i restructured the content. I'm not sure if your answers are still up for that.
Please check my sandbox feel free to fork it here:
https://codesandbox.io/s/get-the-property-value-forked-hutww
So on ContentData.js i want my image alt tag to be dynamic and pull the content of it from key name it something like this alt={this.name} and it will generate to alt="My alt tags"
See the codes below:
Content.js
import React, { Component } from "react";
import mainListsItems from "./ContentData";
class Content extends Component {
render() {
const myContent = mainListsItems.map((lists, k) => (
<>
<div key={k}>{lists.text}</div>
{lists.mainContent.map((subcontent, j) => {
return <div key={j}>{subcontent.contentAll}</div>;
})}
</>
));
return <>{myContent}</>;
}
}
export default Content;
ContentData.js
import React from "react";
const listsData = [
{
id: 1,
name: "My alt tags",
text: (
<>
<p>Lorem Imsum</p>
</>
),
mainContent: [
{
contentAll: (
<>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry.
</p>
<img
alt=""
src="https://images.unsplash.com/photo-1637704758245-ed126909d374?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxNHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&q=60"
/>
</>
)
}
]
}
];
export default listsData;
content is defined inside the object while the object is being defined. So there is no name yet when content is being defined. Only after the assignment does name exist. You're referencing something that doesn't exist yet. So instead you can create a function that will be called at a later point after the object is defined and then the name can be referenced as shown below.
export default function App() {
const myData = [
{
id: 1,
name: "Lorem Ipsum",
content: function(){
return (
<>
<img
src="https://images.unsplash.com/photo-1637704758245-ed126909d374?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxNHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&q=60"
alt={this.name}
/>
</>
)
}
}
];
const output = myData.map((x) => (
<>
<div key={x.id}>
<p>{x.name} sa</p>
<p>{x.content()}</p>
</div>
</>
));
return <div className="App">{output}</div>;
}
The this, in your case is referring the window object.
You can try to pass the name as a function value.
Something like this :
const myData = [
{
id: 1,
name: "Lorem Ipsum",
content: (alt) => (
<>
<img
src="https://images.unsplash.com/photo-1637704758245-ed126909d374?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxNHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&q=60"
alt={alt}
/>
</>
)
}
];
const output = myData.map((x) => (
<div key={x.id}>
<p>{x.name} sa</p>
<p>{x.content(x.name)}</p>
</div>
));
You can replace the object in your myData array with a self executing function like this:
const myData = [
function(){
const entry = {
id: 1,
name: "Lorem Ipsum",
}
return {
...entry,
content: (
<>
<img
src="your image source"
alt={entry.name}
/>
</>
)
}
}()
];

Styling some parts of my React JSX rendered elements with CSS

I have an array of data that I'm mapping through in my small react project to render the contents of the data into the browser. But my problem is that I need to style some parts of these rendered contents and not all. Each of the CSS properties I'm applying is applicable to all the P tags. For Example, in the code below, The style will be applicable to Was, to Birthday in the second item, and so on...
Below is my CSS code.
import React from "react";
import "./styles.css";
export default function App() {
const data = [
{
details: 'Yesterday was his birthday'
},
{
details: 'Today is my birthday'
},
{
details: 'I cant\'t remember my birthday date'
}
]
return (
<div className="App">
<h1>I need Help</h1>
<h2>Please How do i style some parts of my rendered details</h2>
{
data.map((detail)=>(
<p>{detail.details}</p>
))
}
</div>
);
}
You can add a highlight prop to your array elements and split the sentence by that prop. Then, put the split word in its own span between the pieces.
export default function App() {
const data = [
{
details: 'Yesterday was his birthday',
highlight: 'was'
},
{
details: 'Today is my birthday',
highlight: 'birthday'
},
{
details: 'I cant\'t remember my birthday date',
highlight: 'date'
}
]
return (
<div className="App">
<h1>I need Help</h1>
<h2>Please How do i style some parts of my rendered details</h2>
{data.map((datum) => {
const pieces = datum.details.split(datum.highlight);
return (
<p key={datum.details}>
{pieces[0]}
<span style={{ backgroundColor: "#FF0" }}>{datum.highlight}</span>
{pieces[1]}
</p>
);
})}
</div>
);
}
There are many ways to solve this problem. Then one suggested by #Nick is decent. You can also check my suggestion.
export default function App() {
const data = [
{
details: 'Yesterday was his birthday',
css: true
},
{
details: 'Today is my birthday',
css: false
},
{
details: 'I cant\'t remember my birthday date',
css: false
}
]
return (
<div className="App">
<h1>I need Help</h1>
<h2>Please How do i style some parts of my rendered details</h2>
{
data.map((detail)=>(
<p className={detail.css ? 'my-custom-class' : ''}>{detail.details}</p>
))
}
</div>
);
}
With this approach you will not have <p class="undefined">...</p>

Using i18next to replace a variable with a ReactNode element

I have a translation json file with the following translation:
"pageNotFound": {
"description": "The page could not be found. Click {{link}} to return to the home page"
},
The link variable I am wanting to be replaced by a ReactRouter <Link>
I have the following code in my render method which outputs the below picture.
public render() {
const { t } = this.props;
const message = t('pageNotFound.description', { link: <Link to="/">here</Link> });
return (
<div className="body-content">
<div>
{message}
</div>
</div>
);
}
I have played with the <Trans> component and I think this may be a way but it seems like you have to type the full text including <> tags which for my use case is not what i'm after as I want all text to be in the translation json if possible.
Any recommendations are welcome
You should use Trans component for this.
"pageNotFound": {
"description": "The page could not be found. Click <0>here</0> to return to the home page"
},
public render() {
const { t } = this.props;
return (
<div className="body-content">
<div>
<Trans
t={t}
i18nKey="pageNotFound.description"
components={[
<Link key={0} to="/">
here
</Link>,
]}
/>
</div>
</div>
);
}

Is it possible to add content to a global Vue component from a single file comp?

I have made a global component that will render the content we want.
This component is very simple
<template>
<section
id="help"
class="collapse"
>
<div class="container-fluid">
<slot />
</div>
</section>
</template>
<script>
export default {
name: 'VHelp',
};
</script>
I use it inside my base template with
<v-help />
I'm trying to add content to this component slot from another single file component using.
<v-help>
<p>esgssthsrthsrt</p>
</v-help>
But this logically create another instance of my comp, with the p tag inside. Not the correct thing I want to do.
So I tried with virtual DOM and rendering function, replacing slot by <v-elements-generator :elements="$store.state.help.helpElements" /> inside my VHelp comp.
The store helpElements is a simple array with objects inside.
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'example',
},
{
type: 'i',
},
],
},
Then inside my VElementsGenerator comp I have a render function that with render element inside virtual DOM from an object like
<script>
import {
cloneDeep,
isEmpty,
} from 'lodash';
export default {
name: 'VElementsGenerator',
props: {
elements: {
type: Array,
required: true,
},
},
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [];
for (const entry of object) {
const nestedElements = [];
let elementConfig = {};
if (typeof entry.config !== 'undefined') {
elementConfig = cloneDeep(entry.config);
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
generatedElement.push(createElement(
entry.type,
isEmpty(elementConfig) ? entry.value : elementConfig,
nestedElements
));
if (typeof entry.parentValue !== 'undefined') {
generatedElement.push(entry.parentValue);
}
}
if (isNestedElement) {
return generatedElement.length === 1 ? generatedElement[0] : generatedElement;
}
return createElement('div', generatedElement);
},
},
render(createElement) {
if (this.elements) {
return this.iterateThroughObject(this.elements, createElement);
}
return false;
},
};
</script>
This second method is working well but if I want to render complex data, the object used inside the rendering function is very very long and complex to read.
So I'm trying to find another way to add content to a global component used inside a base layout only when I want it on a child component.
I can't use this VHelp component directly inside children comps because the HTML page architecture will be totally wrong.
I'm wondering if this is possible to add content (preferably HTML) to a component slot from a single file comp without re-creating a new instance of the component?
Furthermore I think this is very ugly to save HTML as string inside a Vuex store. So I don't even know if this is possible and if I need to completely change the way I'm trying to do this.
Any ideas ?
In the store, you should only store data and not an HTML structure. The way to go with this problem would be to store the current state of the content of the v-help component in the store. Then, you would have a single v-help component with a slot (like you already proposed). You should pass different contents according to the state in the store. Here is an abstract example:
<v-help>
<content-one v-if="$store.state.content === 'CONTENT_ONE' />
<content-two v-else-if="$store.state.content === 'CONTENT_TWO' />
<content-fallback v-else />
</v-help>
Child element somewhere else:
<div>
<button #click="$store.commit('setContentToOne')">Content 1</button>
</div>
Vuex Store:
state: {
content: null
},
mutations: {
setContentToOne(state) {
state.content = 'CONTENT_ONE';
}
}
Of course it depends on your requirements and especially on how many different scenarios are used if this is the best way to achieve this. If I understood you correctly, you are saving help elements to the store. You could also save an array of currently selected help elements in there and just display them directly in the v-help component.
EDIT:
Of course you can also just save the static component (or its name) in the store. Then, you could dynamically decide in the child components, which content is shown in v-help. Here is an example:
<v-help>
<component :is="$store.state.helpComponent" v-if="$store.state.helpComponent !== null" />
</v-help>
Test Component:
<template>
test component
</template>
<script>
export default {
name: 'test-component'
};
</script>
Child element somewhere else (variant 1, storing the name in Vuex):
<div>
<button #click="$store.commit('setHelpComponent', 'test-component')">Set v-help component to 'test-component'</button>
</div>
Child element somewhere else (variant 2, storing the whole component in Vuex):
<template>
<button #click="$store.commit('setHelpComponent', testComponent)">Set v-help component to testComponent (imported)</button>
</template>
<script>
import TestComponent from '#/components/TestComponent';
export default {
name: 'some-child-component',
computed: {
testComponent() {
return TestComponent;
}
}
};
</script>
Child element somewhere else (variant 3, storing the name, derived from the imported component, in Vuex; I would go with this variant):
<template>
<button #click="$store.commit('setHelpComponent', testComponentName)">Set v-help component to 'test-component'</button>
</template>
<script>
import TestComponent from '#/components/TestComponent';
export default {
name: 'some-child-component',
computed: {
testComponentName() {
return TestComponent.name;
}
}
};
</script>
Vuex Store:
state: {
helpComponent: null
},
mutations: {
setHelpComponent(state, value) {
state.helpComponent = value;
}
}
See also the documentation for dynamic components (<component :is=""> syntax): https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

Categories

Resources