My code fetches a blob of HTML and renders it on the page. When the user selects some text within this blob, I want the selected text to be wrapped in its own span. (This is a "highlighting" feature similar Google Docs' comments system.)
If I were doing this with plain Javascript, I'd mutate the DOM on my own. But I'm not sure how to do this safely in React or where in the component lifetime I'd be able to do so.
Ideally, I could directly manipulate the Element corresponding to my HTML blob and use that directly within render(), but I don't know if this would play well with React's bookkeeping.
How can I do this in React without shooting myself in the foot?
EDIT:
Per request, some sample code. Let's say that our component receives these props:
{
id: 1337,
content: "<p>This is a paragraph with some <strong>markup</strong></p>",
highlights: [],
}
And renders something accordingly:
const Widget = React.createClass({
render() {
return <div dangerouslySetInnerHTML={{__html: this.props.content}} />
},
});
Now the component is updated with highlights: [{ start: 5, end: 10 }]. I want to have chars 5 through 10 wrapped in some <span>.
Is the right way to do this to just parse this.props.content as an Element, add the <span> in the right place, and dangerouslySetInnerHTML at the end?
Maybe you can store your <p> element with the text you want to highlight in your state. Like
this.state = {
willHighlight: <p>This is a <span>paragraph</span> with some <strong>markup</strong></p>
}
And then you can conditionaly render it. I think this is a better approach
Related
I have a list of translated keys and react component content.
e.g :
list = [{ translation: "Hello <1>World</1>", content: "Hello <a href='#'>World</a>" },
{ translation: "A <1>B</1>", content: "A <a href='#'>B</a>" }]
The idea is to show a translated data from the list using the "translation" and the structure of "content" with Trans Component of i18next-react with a map function.
{list.map((item) => (
<Trans i18nKey={item.translation}>
{item.content}
</Trans>))}
Unfortunately, this doesn't seem to work at all, the outcome is only the translated string, without the structure that should came by the React Component content.
Any idea how to work around this?
Your content is a pure string and not a react element. Therefore your code can't work -> it can't extract the a element from the string and replace the <1> with it.
edit below
sample:
{list.map((item) => (
<Trans
i18nKey={item.translation}
defaults={item.translation} // Hello <0>World</>
components={[World]}
</Trans>))}
You need to pass in the components to get interpolated into the "pseudo" tags <0>...details also have a look at https://react.i18next.com/misc/using-with-icu-format (for props defaults, components, values)
Just in case anyone will try to do this in the future:
The way to do so is to use components props as Jamuhl mentioned.
And to pass there an array with the HTML elements of the "item.content".
The way to get the array of HTML elements from a string as we have on "item.content" is by using ReactHtmlParser that parse that string into an HTML elements array!
ReactHtmlParser can be cloned from here: https://www.npmjs.com/package/react-html-parser
For who still have problem.
make sure you have correct order of nested html
like
key ="<0><0/><1>translated here</1></0>";
function createMarkup()
{
return <div><input type="checkbox" /><label>translate here</label></div>;
}
<Trans i18nKey="key" >{ReactHtmlParser(createMarkup())}</Trans>
In my React app, I have stored a text template as an HTML string on the server which is passed up to the client to be rendered. Is it possible to render this HTML string in such a way that it can access it's parent component state/props? I've tried using dangerouslySetInnerHTML and ReactDOMServer.renderToString(), both of which render the string as HTML, but render {props.text} as a literal string, rather than a reference to the component's props.
For example, on the server I have stored:
<div>Welcome to my app</div>
<div>{props.message}</div>
<div>Have fun</div>
And the component
import React from "react"
import { connect } from "react-redux"
const mapStateToProps = (state) => {
return { state }
},
WelomeBody = (props) => {
return (
<div dangerouslySetInnerHTML={{"__html": props.state.welcomeMessageBody}} />
)
}
export default connect(mapStateToProps, null)(WelomeBody)
but this results in:
Welcome to my app
{props.message}
Have fun
Is there a way that this can be rendered so as to access the value of props.message, rather than just rendering it literally?
If what you have in mind is to send down a React component (in JSX syntax) from your server to your client and have the client somehow rehydrate/compile it into an actual, working component, this is not achievable that easily.
When you build and bundle your React app, only components that are statically referenced/imported in your application at compile time can be used at runtime on the browser.
So, in order to dynamically render a component from a template and embed variables into it, your choices are:
Render it into final HTML on your server, send down that HTML and have a simple React component perform dangerouslySetInnerHTML for it. But, like you've already observed, that content has to be the full HTML code, no templates, variables, JSX, etc. Everything is string and HTML at this point, no more processing.
Alternatively, send down a Markdown document and have a Markdown component parse it into HTML and display it.
Create a sophisticated component that can receive a string, parse it, tokenize it, substitute values in it, etc. Essentially, create your own template-processing component that can read a template (the string sent down from your server) and embed the appropriate values into it.
A simple solution (to substitute values into a template) would be:
function Filler(props) {
let html = props.template;
Object.keys(props).forEach(key => {
if (key !== "template")
html = html.replace("{" + key + "}", props[key]);
});
return <div dangerouslySetInnerHTML={{__html: html}} />;
}
and use it like
<Filler template={"<h1>Hello, {firstname} {lastname}</h1>"} firstname="John" lastname="Doe" />
But again, this is far from a full-fledged actual React component.
let's imagine data that would look like that :
{
{
title: "great track",
tags: ["techno"]
},
{
title: "superb track",
tags: ["house", "90s"]
},
...
}
I render that in an html table, I have a component for the whole table, and a sub component for the tr (aka song title and tracks). so on each line I want to allow the users to be able to access a popup in which they can choose one or more tags for a song. I did it using reactstrap, it works ok.
I'm just a little disappointed by performance, it's quite ok, once it's built, but I saw how much longer it was to load when I added my modal on each line. So my first reflex, was to built only one modal in the parent component, and then use it from the sub component, and then I read articles on how, "one should not use the parent instance because it's bad"(tm).
I understand the point about dataflow, but in my example, having a modal waiting on each line while I'm sure I will never have two at the same time on screen feels like a waste of ressources.
Can anyone point me to an elegant way of building that kind of feature, in this particular context ?
Lifting state up to the parent component is a common practice in react, you can read articles in official documentation https://reactjs.org/docs/lifting-state-up.html
But there is one problem, when you use setState in your parent component, your songs table will render again and again, so you should care about it. One of the way is creating PureComponent for Songs table(if there is no changing in props, this component will not rerender)
I think, the code below is one of the way;
class Parent extends React.Component{
state={
tags: [],
songs: {
title: "great track",
tags: ["techno"]
},
{
title: "superb track",
tags: ["house", "90s"]
}
}
handlePopup(data){
this.setState({tags: data});
}
render(){
const {tags, songs} = this.state;
cons isShowModal = tags && tags.length
return (
<div>
{isShowModal && <Modal data={tags} />}
<SongsList data={songs} />
</div>
)
}
}
class Parent extends React.PureComponent{
render(){
const {data} = this.props;
return (
<table><tbody>...render songs data</tbody></table>
)
}
}
Of course using modal in child rows is a waste of resources. you need to add a modal to parent and use props to show/hide it. also, you should use the key prop for your list items.
By default, when recursing on the children of a DOM node, React just iterates over both lists of children at the same time and generates a mutation whenever there’s a difference.
but when children have keys, React uses the key to match children in the original tree with children in the subsequent tree.
it's good for performance.
I'm working on a simple blog/CMS tool. In authoring content, I'm supporting an option to enter raw html/css. The user enters this content into a text area, and I can render this into a page using dangerouslySetInnerHtml. That works fine.
However, I'd really like to embed some React components the content as well. Ideally I'd like to enter something like this into a textarea...
<div>
<p>Some content</p>
<MyPictureComponent url="..." />
</div>
...and then render that into a page and have it create the MyPictureComponent.
I'll be storing the above "code" in a database, and rendering it dynamically as users view the "post". Is it possible to rendering that raw text as functioning React?
I saw this project (HTML to React), which seemed promising, bu that seems to only parse the HTML given to it, not tags for React components.
I found a way to do what I want, with the caveat that it's somewhat manual, and potentially dangerous. However, in my case, I'm creating a blog/CMS for a very limited audience, and the concern about users potentially inserting harmful content is non-existent.
My approach ended up using html-to-react (https://www.npmjs.com/package/html-to-react). Html-to-react accepts a string (containing raw HTML markup), and transforms it into a proper React component. By default, its parse() method doesn't properly handle React components (it just turns them into lower-case-named html elements). However, the library has a parseWithInstructions, which allows you to control how individual nodes in the component are rendered.
In my case, I want to enable certain React components to be rendered. One of those is my ExternalLink component. What follows is the method I use to transform some user-entered raw HTML into a React component that properly rendered my components.
updatePreview() {
// Combine the user-entered CSS and the user-entered HTML into a single string.
let outputPreview = "<div><style>" + this.state.cssValue + "</style><div>" + this.state.inputValue + "</div></div>";
let htmlToReactParser = new HtmlToReact.Parser();
let processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React);
let processingInstructions = [
{
// Custom <ExternalLink> processing
shouldProcessNode: function (node) {
return node.name === 'externallink';
},
processNode: function (node, children) {
let attribs = node.attribs;
return <ExternalLink {...attribs}>{children}</ExternalLink>
}
},
{
// Anything else
shouldProcessNode: function (node) {
return true;
},
processNode: processNodeDefinitions.processDefaultNode
}];
// Convert the HTML into a React component
let reactComponent = htmlToReactParser.parseWithInstructions(outputPreview, () => true,
processingInstructions);
// Now that we have a react component, we set it to the state.
// Our render() method includes a "{this.state.outputPreview}", which causes the
// component to be rendered.
this.setState({outputPreview: reactComponent, refreshPreviewTimer: null});
}
Note that outputString in the first line of the method will contain some raw text like this:
"<div>
<style></style>
<div>
<p>Here's a link:<p>
<ExternalLink url="http://www.google.com">Google</ExternalLink>
</div>
</div>"
There are some approaches I'll take to generalize this approach more, using a dictionary of strings to enable support for a wider range of Components. I'll also look at some approach to automatically importing the desired Component. (Currently, I'm manually importing all supported components.)
So, all credit goes to the author of html-to-react, though I may encourage him to include an example of rendering child components.
I've been working on a React project recently. I've gotten to the point where I know that when typing in a textbox React updates its UI state and re-renders the textbox and brings focus back to it. The whole process happens so fast that user doesn't notice that textbox has been re-rendered while typing.
Now here my question is that why doesn't React just update UI state of textbox at onChange event and doesn't re-render the textbox and hence doesn't require to put focus on it.
Why isn't the re-render process saved in this, or other similar, cases. What were we missing if we dont re-render?
React doesn't re-render elements in the sense that it creates a new one which replaces the old. In your example it simply updates the value.
React will only ever remove an element if it was replaced with one of another type. If it's of the same type, it will simply update the missing or altered attributes. You should have a read about Reacts Reconciliation on the official docs.
Elements Of Different Types
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. Going from <a> to <img>, or from <Article> to <Comment>, or from <Button> to <div> - any of those will lead to a full rebuild.
DOM Elements Of The Same Type
When comparing two React DOM elements of the same type, React looks at the attributes of both, keeps the same underlying DOM node, and only updates the changed attributes.
Demo
I have prepared a demo below. It's a combination of React with jQuery just to demonstrate this.
When you type some text into the textarea, the state is updated and the element value is updated to show what you typed. This is done with React.
The button is outside the React scope completely. There is an eventlistener written with jQuery which randomly alters the elements css background color.
If the component was replaced each time React updated the state value of the component (i.e when we type inside the textarea), the background color would be lost since we would be talking about a newly created DOM element.
However, as you can see, this is not the case -- the background stays.
class Greeting extends React.Component {
constructor() {
super();
this.state = {txt: ""};
}
updateTextarea = (e) => {
this.setState({txt: e.target.value});
}
render() {
return <textarea onChange={this.updateTextarea}>{this.state.txt}</textarea>;
}
}
ReactDOM.render(<Greeting />, document.getElementById('app'));
$("#btn").on("click", function() {
var colors = ["red", "green", "lightblue", "grey", "pink", "orange"];
var rand = Math.floor(Math.random() * 6);
$("#app textarea").css("background", colors[rand]);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<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>
<div id="app"></div>
<button id="btn">Randomize color!</button>