i have defined an image in spinner.js like so:
import React from 'react';
import broomAndText from './mygif.gif';
function myGif() {
return <img src={broomAndText} alt="broomAndText" width="200px" height="200px" />
}
export default myGif;
i then want to call the image, and display it on page in my mainPage.js. Here's how I am trying to call it:
import myGif from './spinner'
var regData = ""
firestore.collection("profiledata").doc(user.uid).get().then((doc) => {
var firstName = doc.data().firstname;
var lastName = doc.data().lastname;
var companyName = doc.data().companyname;
var email = doc.data().email;
console.log(doc.data().registeringFlag);
regData = doc.data().registeringFlag;
console.log("reg data " + regData)
if (regData == "yes"){
regData = {myGif}
}
}).then(() => {
if (regData != "no") {
document.write(regData)
}
})
what am I doing wrong? It is just outputting [object Object]
I think what You need is to render your react element properly.
Instead of just writing text into html via document.write() You need to render it to some existing DOM element. For instance, a div inside a body:
import ReactDOM from 'react-dom'
import MyGif from './spinner'
const div = document.createElement('div')
div.id = '__root'
document.body.appendChild(div)
ReactDOM.render(<MyGif/>, document.getElementById('__root'))
Also please note that I replace your camelCase notation for a myGif with PascalCase, as react will interpret it incorrectly the other way.
You also need to put your element inside xml tag (<> braces) to actually create it and make it work.
As first step You may just replace document.write(regData) with ReactDOM.render(myGif, document.getElementsByTagName('body') and it should work.
But in perspective it would be much better to check some basic react tutorials on how the rendering process works in react. It is not as simple as putting plain html into document.
Related
The following code renders a warning message conditionally, when user switch site (by clicking the button, or accessing directly using URL), the website will show a warning message, and disappear on refresh.
Things work fine within the docusaurus local development server, but behaves differently once built into a production static site.
import Cookies from 'js-cookie';
import React from 'react';
import { DocProvider } from '#docusaurus/theme-common/internal';
import { HtmlClassNameProvider } from '#docusaurus/theme-common';
import { translate } from '#docusaurus/Translate';
import CommunityLinkGroup from "#site/src/components/LinkGroup";
import DocItemLayout from '#theme/DocItem/Layout';
import DocItemMetadata from '#theme/DocItem/Metadata';
import styles from "./index.module.css";
export default function DocItem(props) {
const docHtmlClassName = `docs-doc-id-${props.content.metadata.unversionedId}`;
const MDXComponent = props.content;
const url = props.location.pathname;
const prevEdition = Cookies.get('doc_edition');
var displayAlert = false;
if (url.includes('/community/')) {
Cookies.set('doc_edition', 'community');
if (prevEdition == 'cloud') {
displayAlert = true;
}
} else if (url.includes('/cloud/')) {
Cookies.set('doc_edition', 'cloud');
if (prevEdition == 'community') {
displayAlert = true;
}
}
var alertMsg = "bad msg";
var alertClass = "badcls";
var alertRole = "badrole";
if (displayAlert == true) {
alertMsg = translate({
id: 'theme.DocItem.switchDocAlertMsg',
message: 'you just switched site, please notice.',
});
alertClass = "alert alert--warning";
alertRole = "alert";
}
console.log(alertMsg, alertClass, alertRole);
return (
<DocProvider content={props.content}>
<HtmlClassNameProvider className={docHtmlClassName}>
<div className={alertClass} role={alertRole}>{alertMsg}</div>
<DocItemMetadata />
<DocItemLayout>
<MDXComponent />
</DocItemLayout>
<div className={styles.communityLinkContainer}>
<CommunityLinkGroup />
</div>
</HtmlClassNameProvider>
</DocProvider>
);
}
Works fine inside the local dev server:
But once built into a production static site, it yields impossible rendering result:
Meanwhile, production site console output shows you just switched site, please notice. alert alert--warning alert, which clearly indicates alertMsg=='you just switched site, please notice.', alertClass=="alert alert--warning", alertRole=="alert", meaning that displayAlert must be true.
But as shown in above screenshot, it looks like as if displayAlert is both false and true at the same time, a totally impossible DOM state.
Also, this only happens when accessed directly using URL path, if I click on the button provided by the website to change site, the website warning message will display normally through dynamic rendering.
I should be using state variables, like https://stackblitz.com/edit/react-pcagrw?file=src/App.js, consider this a React 101.
Im changing a child component state from its parent using this method:
From parent:
this.changeBottomToolbar(newTool)
Method declared on Child's class
changeBottomToolbar(newToolbarName){
this.setState({selectedToolbar: newToolbarName})
}
It's state change and it re-renders as I check with the console.
The render it's a conditional render that uses a function to get what it should render
getBottomToolbar(){
switch(this.state.selectedToolbar){
case "SelectorAndText":
console.log("Devolviendo el selector")
return <TextBottomToolbar
ref={this.bottomToolbarRef}
fontSizeUpdater = {this.fontSizeUpdater}
fontColorUpdater = {this.fontColorUpdater}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
fontFamilyUpdater = {this.fontFamilyUpdater}
alignmentUpdater = {this.alignmentUpdater}
/>
break
case "FreeLine":
console.log("Devolviendo el Line")
return <LineBottomToolbar
ref={this.bottomToolbarRef}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
shadowColorUpdater={this.shadowColorUpdater}
shadowSizeUpdater={this.shadowSizeUpdater}
/>
break
}
}
And i call it on the render:
render(){
const { classes } = this.props
console.log(this.state.selectedToolbar)
return (
<React.Fragment>
{
this.getBottomToolbar()
}
</React.Fragment>
);
}
As you can see in this image, the code it's been executed correctly and it returns the other component when the state it's changed
But the component ITS NOT CHANGING even tho the render it's been called again and it's state it's changing, im completely shocked, I have no clue on why this happens, please help!!
So the problem was that i wrongly copy-pasted the import (facepalm)
This was the import I was doing so I thought it wasn't chaning
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/TextBottomToolbar'
This corrected the problem:
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/LineBottomToolbar '
In React JSX I want to convert a part of the text into an anchor tag dynamically. Also on click of the anchor tag, I want to do some API call before redirecting it to the requested page. But I am failing to achieve this. Can anyone have a look and let me know where am I going wrong?
I have recreated the issue on code sandbox: here is the URL: Code Sandbox
Relevant code from sandbox:
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let start = 22;
let end = 26;
let anchorText = bodyTextProp.substring(start, end);
let anchor = `<a
href="www.test.com"
onClick={${(e) => handleClick(e)}}
>
${anchorText}
</a>`;
bodyTextProp = bodyTextProp.replace(anchorText, anchor);
const handleClick = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3 dangerouslySetInnerHTML={{ __html: bodyTextProp }} />
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
The problem is variable scope. While it is entirely possible to use the dangerouslySetInnerHTML as you are doing, the onClick event isn't going to work the same way. It's going to expect handleClick to be a GLOBAL function, not a function scoped to the React component. That's because React doesn't know anything about the "dangerous" html.
Normally React is using things like document.createElement and addEventListener to construct the DOM and add events. And since it's using addEventListener, it can use the local function. But dangerouslySetInnerHTML bypasses all of that and just gives React a string to insert directly into the DOM. It doesn't know or care if there's an event listener, and doesn't try to parse it out or anything. Not really a good scenario at all.
The best solution would be to refactor your code so you don't need to use dangerouslySetInnerHTML.
*Edit: since you say that you need to do multiple replacements and simply splitting the string won't suffice, I've modified the code to use a split.
When used with a RegExp with a capturing group, you can keep the delimiter in the array, and can then look for those delimiters later in your map statement. If there is a match, you add an a
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let rx = /(\bLINK\b)/;
let array = bodyTextProp.split(rx);
const handleClick = (e) => {
console.log("The link was clicked.");
e.preventDefault();
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3>
{array.map((x) => {
if (rx.test(x))
return (
<a href="www.test.com" onClick={handleClick}>
{x}
</a>
);
else return x;
})}
</h3>
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
in my React app I am getting some HTML from another server (Wikipedia) and - in this text - want to replace all the links with react-router links.
What I have come up with is the following code:
// Parse HTML with JavaScript DOM Parser
let parser = new DOMParser();
let el = parser.parseFromString('<div>' + html + '</div>', 'text/html');
// Replace links with react-router links
el.querySelectorAll('a').forEach(a => {
let to = a.getAttribute('href');
let text = a.innerText;
let link = <Link to={to}>{text}</Link>;
a.replaceWith(link);
});
this.setState({
html: el.innerHTML
})
Then later in render() I then inserted it into the page using
<div dangerouslySetInnerHTML={{__html: this.state.html}} />
The problem is that React JSX is not a native JavaScript element thus not working with replaceWith. I also assume that such a Link object can not be stored as text and then later be restored using dangerouslySetInnerHTML.
So: what is the best way to do this method? I want to keep all the various elements around the link so I cannot simply loop through the links in render()
Umm, you really need to use Link
Option 1:
import { renderToString } from 'react-dom/server';
el.querySelectorAll('a').forEach(a => {
let to = a.getAttribute('href');
let text = a.innerText;
const link = renderToString(<Link to={to}>{text}</Link>);
a.replaceWith(link);
});
Option 2: Use html-react-parser
You can not render <Link> like this.
Instead you could try another way:
el.querySelectorAll('a').forEach((a) => {
a.addEventListener('click', (event) => {
event.preventDefault()
const href = a.getAttribute('href');
// use React Router to link manually to href
})
})
when click on <a> you routing manually.
See Programmatically navigate using react router
You can leverage functionality of html-react-parser
import parse, { domToReact } from 'html-react-parser';
import { Link } from 'react-router-dom';
function parseWithLinks(text) {
const options = {
replace: ({ name, attribs, children }) => {
if (name === 'a' && attribs.href) {
return <Link to={attribs.href}>{domToReact(children)}</Link>;
}
}
};
return parse(text, options);
}
And then use it like:
const textWithRouterLinks = parseWithLinks('Home page');
I'm displaying text that was stored in the database. The data is coming from firebase as a string (with newline breaks included). To make it display as HTML, I originally did the following:
<p className="term-definition"
dangerouslySetInnerHTML={{__html: (definition.definition) ? definition.definition.replace(/(?:\r\n|\r|\n)/g, '<br />') : ''}}></p>
This worked great. However there's one additional feature. Users can type [word] and that word will become linked. In order to accomplish this, I created the following function:
parseDefinitionText(text){
text = text.replace(/(?:\r\n|\r|\n)/g, '<br />');
text = text.replace(/\[([A-Za-z0-9'\-\s]+)\]/, function(match, word){
// Convert it to a permalink
return (<Link to={'/terms/' + this.permalink(word) + '/1'}>{word}</Link>);
}.bind(this));
return text;
},
I left out the this.permalink method as it's not relevant. As you can see, I'm attempting to return a <Link> component that was imported from react-router.However since it's raw HTML, dangerouslySetInnerHTML no longer works properly.
So I'm kind of stuck at this point. What can I do to both format the inner text and also create a link?
You could split the text into an array of Links + strings like so:
import {Link} from 'react-router';
const paragraphWithLinks = ({markdown}) => {
const linkRegex = /\[([\w\s-']+)\]/g;
const children = _.chain(
markdown.split(linkRegex) // get the text between links
).zip(
markdown.match(linkRegex).map( // get the links
word => <Link to={`/terms/${permalink(word)}/1`}>{word}</Link> // and convert them
)
).flatten().thru( // merge them
v => v.slice(0, -1) // remove the last element (undefined b/c arrays are different sizes)
).value();
return <p className='term-definition'>{children}</p>;
};
The best thing about this approach is removing the need to use dangerouslySetInnerHTML. Using it is generally an extremely bad idea as you're potentially creating an XSS vulnerability. That may enable hackers to, for example, steal login credentials from your users.
In most cases you do not need to use dangerouslySetHTML. The obvious exception is for integration w/ a 3rd party library, which should still be considered carefully.
I ran into a similar situation, however the accepted solution wasn't a viable option for me.
I got this working with react-dom in a fairly crude way. I set the component up to listen for click events and if the click had the class of react-router-link. When this happened, if the item has a data-url property set it uses browserHistory.push. I'm currently using an isomorphic app, and these click events don't make sense for the server generation, so I only set these events conditionally.
Here's the code I used:
import React from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router'
export default class PostBody extends React.Component {
componentDidMount() {
if(! global.__SERVER__) {
this.listener = this.handleClick.bind(this);
window.addEventListener('click', this.listener);
}
}
componentDidUnmount() {
if(! global.__SERVER__) {
window.removeEventListener("scroll", this.listener);
}
}
handleClick(e) {
if(_.includes(e.target.classList, "react-router-link")) {
window.removeEventListener("click", this.listener);
browserHistory.push(e.target.getAttribute("data-url"));
}
}
render() {
function createMarkup(html) { return {__html: html}; };
return (
<div className="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-3 col-lg-8 col-lg-offset-2 post-body">
<div dangerouslySetInnerHTML={createMarkup(this.props.postBody)} />
</div>
);
}
}
Hope this helps out!