Render before or after child element - javascript

How do I render before or after a child element in a container?
I am learning React by integrating it into my own website. I started with this:
function createErrorSection(name, title, description) {
const section = document.createElement('section');
const container = document.createElement('div');
const h2 = document.createElement('h2');
const p = document.createElement('p');
section.appendChild(container);
container.appendChild(h2);
container.appendChild(p);
section.id = name;
section.classList = 'section-error';
container.classList = 'section-error-container';
h2.textContent = title;
p.textContent = description;
return section;
}
Which I turned into this:
function createErrorSection(name, title, description) {
return (
<section id={name} className='section-error'>
<div className='section-error-container'>
<h2>{title}</h2>
<p>{description}</p>
</div>
</section>
);
}
This is eventually propagated down to either node.before(section) or node.after(section).
I checked inside ReactDOM, ReactDOM/server and React with no luck. I saw I could create an HTML string, but I need an HTMLElement and would rather not do my own rendering if it can be avoided (I want to learn the React way, I already know the vanilla way).
My end goal is to learn how and when to use React properly. I'd love to know the proper way, but insight, advice and workarounds are also greatly appreciated!

In React you rather want to create a custom component with a single argument which contains the corresponding properties:
// single argument contains all props
function ErrorSection({name, title, description}) {
return (
<section id={name} className='section-error'>
<div className='section-error-container'>
<h2>{title}</h2>
<p>{description}</p>
</div>
</section>
);
}
now you need to import ReactDOM and call render in order to show the component ErrorSecion with some specific property values inside a HTML node with the id #app. Make sure that your HTML document contains such a node.
import ReactDOM from "react-dom";
ReactDOM.render(
<ErrorSection name="..." title="..." description="..." />,
document.querySelector("#app")
);
Most of the react apps render some dynamically generated nested components into the DOM using a single empty HTML node inside the document body (e.g. div#app or div#root). So you most likely will only need to have a single ReactDOM.render call in your entire project.

First of all, component's name should be written in PascalCase.
In React, you should rethink the way you render elements.
There are different approaches for different purposes:
Pass components to the children prop
const Wrapper = ({ children }) => (
<div className="wrapper">
<h1>Wrapper heading</h1>
{children}
</div>
);
Now you can pass children to the wrapper this way:
const AnotherComponent = () => (
<Wrapper>
<div>Element that will be rendered where the children prop is placed</div>.
</Wrapper>
);
Pass components to custom props:
If you need to render many components in different spots, you can do this:
const MultiSpotComponent = ({ HeaderComponent, FooterComponent }) => (
<div>
{HeaderComponent}
<div>Some content</div>
{FooterComponent}
</div>
);
And then pass your components to the props the same way you do with attributes in HTML:
<MultiSpotComponent HeaderComponent={CustomHeader} FooterComponent={CustomFooter} />
Notice that I used self-closing tag for the component, because I don't render children inside it.
Render list
const AnotherComponent = () => {
const dynamicArray = ['some', 'dynamic', 'values'];
return (
<div>
{dynamicArray.map(value => <div key={value}>{value}</div>)}
</div>
);
};
I have described only 3 most-used approaches, but there are more ways to render elements. You can learn more at Official React Documentation

Related

How do you get an HTML string from JSX without React. I'm using SolidJS for example

I'm not using React, so this won't work
ReactDomServer.renderToString(<div>p</div>)
I'm currently rendering the jsx in a hidden div with an id on the browser and then using
document.getElementById(id).outerHTML
to get the HTML, but I'm wondering if there's a more elegant solution
In SolidJS components actually render as plain DOM nodes, so you can actually just use all of the DOM nodes properties
In your specific example that could be something like
const SomeComponent = <div></div>;
console.log(SomeComponent.outerHTML) //this will output -> "<div></div>"
I hope this helps!
I'm currently rendering the jsx in a hidden div with an id on the browser and then using
document.getElementById(id).outerHTML
to get the HTML, but I'm wondering if there's a more elegant solution
JSX is not directly supported by the browser so it requires a compiler to be compiled into proper HTML that means you have to use a library like Solid, React ect.
In Solid, you don't need to render it into a hidden div, just don't output it to the DOM.
import { render, renderToString } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
return (
<button type="button" onClick={increment}>
{count()}
</button>
);
}
function App() {
let el = <Counter />
const increment = () => {
console.log((el.outerHTML));
};
return (
<button type="button" onClick={increment}>
get content
</button>
);
}
render(() => <App />, document.getElementById("app")!);
There is renderToString in Solid to support server side rendering, but it appears it does not work in browsers.

React and using ReactDOM.createPortal() - Target container is not a DOM element Error

I'm working on a React app and am trying to use ReactDOM.createPortal() to add html content to a div that is outside the component (called ToDoItem).
{ReactDOM.createPortal(
<Route path={`/profile/project/${this.props.project._id}`} render={() =>
<ProjectView project={this.props.project}/>} />,
document.getElementById('tasks')
)}
None of the HTML in the public folder is predefined - it is all dynamically created.
I think this could be the cause of the error: React tries to add HTML to the div with the id of tasks which, but the div is not loaded into the DOM before this happens?
If this method is incorrect, is there any other method I can use append html content to another div outside the component?
Some other info: the component from which I tried to run this method is a stateless component, not a class component.
This is the error:
You can wait until the DOM is ready using React.useEffect, and then you call ReactDOM.createPortal:
function Component() {
const [domReady, setDomReady] = React.useState(false)
React.useEffect(() => {
setDomReady(true)
})
return domReady
? ReactDOM.createPortal(<div>Your Component</div>, document.getElementById('container-id'))
: null
}
The problem is that you can't createProtal to react component.
the second parameter have to be dom elemenet, not react created element
I had this issue because I forgot to add <div id="some-id"></div> to my index.html file :-D
So just as a reminder for anyone who has a similar problem or doesn't know how to use React portals ( to create a modal for example):
in modal.jsx:
const modalRoot = document.getElementById('modal');
const Modal = () => {
const modalElement = document.createElement('div');
// appends the modal to portal once modal's children are mounted and
// removes it once we don't need it in the DOM anymore:
useEffect(() => {
modalRoot.appendChild(modalElement);
return () => {
modalRoot.removeChild(modalElement);
};
}, [modalElement]);
return createPortal(<div>modal content</div>, modalRoot);
};
in index.html:
<!DOCTYPE html>
<html lang="en">
<head>
// head content
</head>
<body>
<div id="root"></div>
// dont't forget about this:
<div id="modal"></div>
</body>
</html>
Before first render of components, there is no element in the DOM and document.getElementById cannot reach to any element. elements added to DOM after first render.
Use useRef in parent component and send it to Portal Component.
In parent component:
const tasksRef = React.useRef(null);
const [tasksSt, setTasksSt]= React.useState();
....
<div ref={
(current) => {tasksRef.current = current;
setTasksSt(tasksRef.current);
}
}/>
<YourPortalComp tasksRef={tasksRef} />
In Portal Component
{this.props.tasksRef.current?(ReactDom.createPortal(<...>, this.props.tasksRef.current):null}
If the target element (In your case element with id tasks) is loaded but still you are getting the Target container is not a DOM element Error error, you can try the below solution.
const MyElement = () => {
const [containerToLoad, setContainerToLoad] = useState(null);
// If the target element is ready and loaded
// set the target element
useEffect(() => {
setContainerToLoad(document.getElementById('container'));
}, []);
return containerToLoad && createPortal(<div>modal content</div>, containerToLoad);
};
check your id in index.html file,
So lets say, your index.html file has:
<div id="overlays"></div>
Your Modal should point to same div, ie
const portalElement = document.getElementById('overlays');
const Modal = (props) => {
return (
<Fragment>
{ReactDOM.createPortal(<Backdrop onClose={props.onClose} />, portalElement)}
</Fragment>
);
};
For those that might be using Next.js, an equivalent to AlexxBoro's answer for next js can be found here. https://www.learnbestcoding.com/post/101/how-to-create-a-portal-in-next-js

Why does my React grandchild component not update after receiving a change in props from its parent? Or: do I need to use state?

I have tried finding the answer to this on StackOverflow and there are some related posts (e.g. React Child Component Not Updating After Parent State Change) but I want to understand why this is not working...
I have a React application that will display a layout of character cards (that is, each card displays a different character). It uses a child component, CharacterBoard, that lays out the CharacterCards, which would be a grandchild component. I pass the characters down from the App to the CharacterBoard as props, and CharacterBoard in turn maps these out the CharacterCards.
The problem is that I want the state of the character to change when I click on one of them. Specifically, I want the revealed field to change. However, even though the state change is reflected in the array of characters in the App (that is, the revealed field changes correctly), and the change is reflected in the array of characters in CharacterBoard, but not in CharacterCard. In fact, my mapping does not seem to be called at all in CharacterBoard when the props change.
Do I need to use something like getDerivedStateFromProps in CharacterBoard and set the state of that component and then use the state to map the values down to CharacterCard? If so, why?
In short (tl;dr), can you pass props on down through the component chain and map them out along the way and still have all changes reflected automatically?
Thanks for any guidance.
If it helps, the render method of my App is
render() {
const {state: {characters}} = this
return (
<div>
<header>
</header>
<main>
<CharacterBoard
onCardSelected={this.onCardSelected}
rowSize={logic.ROW_SIZE}
characters={characters}
cardSize={this.CARD_SIZE}/>
</main>
</div>
);
}
that of CharacterBoard is
render() {
const {props: {characters, rowSize, cardSize,onCardSelected}} = this
const rowUnit = 12 / rowSize
const cardLayout = characters
.map((character, i) => (
<Col xs={6} sm={rowUnit} key={character.name}>
<CharacterCard
onCardSelected = {onCardSelected}
key={i + Math.random()}
character={character}
cardSize={cardSize}
/>
</Col>
)
)
return (
<div>
<Container>
<Row>
{cardLayout}
</Row>
</Container>
</div>
)
}
and finally CharacterCard has this render method
render() {
const {props: {character, cardSize}} = this
const {thumbnail, revealed} = character
const imgURL = `${thumbnail.path}/${cardSize}.${thumbnail.extension}`
const topCardClass = classNames('characterCard__card-back', {'characterCard__card-back--hidden': revealed})
console.log(revealed)
return < a href="/#" onClick={this.onCardSelected}>
<div className='characterCard__card'>
<div className={topCardClass}>
<img src="/images/card_back.png" alt=""/>
</div>
< div className='characterCard__card-front'>< img alt=''
src={imgURL}/>
</div>
</div>
</a>
}
Doh! A simple forgetting to setState in App. Knowing that it should work made me go back through the code one more time and see that, indeed, it was a stupid error on my part.

React JS | Render Multiple Elements

I am trying to create a email in React using the MJML email library. It runs off react and I have it all working but I need to render 2 sections rather than 1. When I render 1 it doesn't appear properly on the webpage as I need them to be different sizes.
When I try and wrap the elements within a array the return become null, take out one of the sections and it gets returned.
Any help would be appreciated, here is the code.
render() {
const { mjAttribute } = this.props
const content = [this.renderEmailOverhead()]
const innerContent = [this.renderEmailBanner(), this.renderEmailTitle(), this.renderEmailText(), this.renderEmailDivider]
return ([
<Section full-width='full-width' padding-top="0">
{ content }
</Section>,
<Section>
{ innerContent }
</Section>
])
}
Well, render method of a component can only return one element. so you'll have to wrap it in a divas Zargold mentioned.
Note that MJML component are more than a standard React component.
It has some internal logic not available in a React context. IMO you should generate MJML as standard HTML element and render it with a renderToStaticMarkup then pass it to mjml2html function as a string and mjml will compiles
return (
<mjml>
<mj-body>
<mj-container>
... // your sections goes here
</mj-container>
</mj-body>
</mjml>
)
Note that I don't think React is the best suited for this kind of work, I would recommend you to use a templating language such as mustache/handlebars which fit better.
You cannot use JSX interspersed with JavaScript like that... you could either do (you must have only one parent/root element).
<div>
<Section full-width='full-width' padding-top="0">
{ content }
</Section>
<Section>
{ innerContent }
</Section>
</div>
Or You could if you insist on using an array for some reason:
renderSection(content, fullWidth){
return (
<Section
full-width={fullWidth ? 'full-width' : false}
style={{paddingTop: fullWidth ? 0 : 'auto'}}
>
{content}
</Section>
)
}
render(){
let contents = [content, innerContent]
return(
<div>
{contents.map(section, i => renderSection(section, i % 2 === 0))
</div>
)

Insert HTML with React Variable Statements (JSX) [duplicate]

This question already has answers here:
ReactJS convert HTML string to JSX
(12 answers)
Closed 2 years ago.
I am building something with React where I need to insert HTML with React Variables in JSX. Is there a way to have a variable like so:
var thisIsMyCopy = '<p>copy copy copy <strong>strong copy</strong></p>';
and to insert it into react like so, and have it work?
render: function() {
return (
<div className="content">{thisIsMyCopy}</div>
);
}
and have it insert the HTML as expected? I haven't seen or heard anything about a react function that could do this inline, or a method of parsing things that would allow this to work.
You can use dangerouslySetInnerHTML, e.g.
render: function() {
return (
<div className="content" dangerouslySetInnerHTML={{__html: thisIsMyCopy}}></div>
);
}
Note that dangerouslySetInnerHTML can be dangerous if you do not know what is in the HTML string you are injecting. This is because malicious client side code can be injected via script tags.
It is probably a good idea to sanitize the HTML string via a utility such as DOMPurify if you are not 100% sure the HTML you are rendering is XSS (cross-site scripting) safe.
Example:
import DOMPurify from 'dompurify'
const thisIsMyCopy = '<p>copy copy copy <strong>strong copy</strong></p>';
render: function() {
return (
<div className="content" dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(thisIsMyCopy)}}></div>
);
}
dangerouslySetInnerHTML has many disadvantage because it set inside the tag.
I suggest you to use some react wrapper like i found one here on npm for this purpose.
html-react-parser does the same job.
import Parser from 'html-react-parser';
var thisIsMyCopy = '<p>copy copy copy <strong>strong copy</strong></p>';
render: function() {
return (
<div className="content">{Parser(thisIsMyCopy)}</div>
);
}
Very Simple :)
UPDATE
in the latest version as usage explained:
// ES Modules
import parse from 'html-react-parser';
// CommonJS
const parse = require('html-react-parser');
....
//Parse single element
parse('<li>Item 1</li><li>Item 2</li>');
//Parse multiple elements
parse('<li>Item 1</li><li>Item 2</li>');
By using '' you are making it to a string. Use without inverted commas it will work fine.
const App = () => {
const span = <span> whatever your string </span>
const dynamicString = "Hehe";
const dynamicStringSpan = <span> {`${dynamicString}`} </span>
return (
<div>
{span}
{dynamicStringSpan}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
import { Fragment } from 'react' // react version > 16.0
var thisIsMyCopy = (
<Fragment>
<p>copy copy copy
<strong>strong copy</strong>
</p>
</Fragment>
)
By using '' the sets the value to a string and React has no way of knowing that it is a HTML element. You can do the following to let React know it is a HTML element -
Remove the '' and it would work
Use <Fragment> to return a HTML element.
To avoid linter errors, I use it like this:
render() {
const props = {
dangerouslySetInnerHTML: { __html: '<br/>' },
};
return (
<div {...props}></div>
);
}
You don't need any special library or "dangerous" attribute. You can just use React Refs to manipulate the DOM:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.divRef = React.createRef();
this.myHTML = "<p>Hello World!</p>"
}
componentDidMount() {
this.divRef.current.innerHTML = this.myHTML;
}
render() {
return (
<div ref={this.divRef}></div>
);
}
}
A working sample can be found here:
https://codepen.io/bemipefe/pen/mdEjaMK
Try Fragment, if you don't want any of above.
In your case, we can write
import React, {useState, Fragment} from 'react'
const thisIsMyCopy = Fragment('<p>copy copy copy <strong>strong copy</strong></p>')
render: function() {
return (
<div className="content">{thisIsMyCopy}</div>
);
}
If you using hook want to set it in a state somewhere with any condition
const [thisIsMyCopy, setThisIsMyCopy] = useState(<Fragment><p>copy copy copy <strong>strong copy</strong></p></Fragment>);
If anyone else still lands here. With ES6 you can create your html variable like so:
render(){
var thisIsMyCopy = (
<p>copy copy copy <strong>strong copy</strong></p>
);
return(
<div>
{thisIsMyCopy}
</div>
)
}
You can also include this HTML in ReactDOM like this:
var thisIsMyCopy = (<p>copy copy copy <strong>strong copy</strong></p>);
ReactDOM.render(<div className="content">{thisIsMyCopy}</div>, document.getElementById('app'));
Here are two links link and link2 from React documentation which could be helpful.

Categories