How to display nested HTML content with {this.props.children}? - reactjs

I'm trying to display standard HTML content that is nested within a React modal component. I thought using {this.props.children} within the component would allow the nested HTML to be displayed, but the paragraph doesn't appear within the modal when it's open. I want to keep the content within the HTML file and out of the React Component.
A codepen of the issue is here.
HTML:
<div id="testModal">
<p>Content to be displayed within modal</p>
</div>
JS:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false
};
// bind functions
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
}
// close modal
closeModal() {
this.setState({isModalOpen: false});
}
// open modal
openModal() {
this.setState({isModalOpen: true});
}
render() {
return (
<React.Fragment>
<div className="link" onClick={this.openModal}>Click Me</div>
<div id="modal" className="modal__outter" style={{
display: this.state.isModalOpen
? 'block'
: 'none'
}}>
<div className="modal__overlay" onClick={this.closeModal}></div>
<div onClick={this.state.closeModal}></div>
<div className="modal__container">
<div className="modal__header">
<button onClick={this.closeModal} className="link">X</button>
</div>
<div className="modal__content">
{this.props.children}
</div>
<div className="modal__footer">
<button className="link" onClick={this.closeModal}>Close</button>
</div>
</div>
</div>
</React.Fragment>
);
}
}
ReactDOM.render(
<Modal />,
document.getElementById('testModal')
);

this.props.children refers to component passed in from your React code, not from HTML file.
So pass the modal content in ReacDOM.render
ReactDOM.render(
<Modal>
<p>Content to be displayed within modal</p>
</Modal>,
document.getElementById('testModal')
);
Here is the forked CodePen.
Answer to the comment is there a way to pass the content in from the HTML file
Yes, you can but you still need to pass the content somehow.
I've used document.getElementById to get the modal content, and passed it to <Modal> as a child.
(If you are planning to use this.props.children that is. Or else you can just get the content from HTML with document.get/querySelect...)
Here is the updated CodePen.

Related

component is unmounted and constructor is called when rendering it in a different place in the DOM

I am trying to implement a minimize/maximize feature in React. I have a div that serves as an information panel and by clicking on a button I would like it to toggle between maximized / minimized states. My top level app component has a boolean state field (maximizedInfo in the example below) that tracks whether the information panel is maximized or not and accordingly renders either just the panel or the full grid of my application with many other DOM elements. The below code is obviously a minified example but the main idea is that the render() method of my top-level component generates two very different DOM trees depending on the state. Unfortunately, I have discovered that my information panel component keeps getting unmounted and the constructor is called on every state change, thus losing the state the information panel component had accumulated.
What is the proper way to address that and implement this sort of functionality in React?
const React = require('react');
class InformationPanel extends React.Component {
constructor(props) {
console.log('InformationPanel:: constructor'); // this keeps getting called
super(props);
}
render() {
return (
<div>
<a id='information' class='nav-link' href="#" onClick={this.props.toggleInfoPanel}>toggle</a>
short info
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maximizedInfo: false
};
this.toggleInfoPanel = this.toggleInfoPanel.bind(this);
}
toggleInfoPanel() {
this.setState({maximizedInfo: !this.state.maximizedInfo});
}
render() {
if (this.state.maximizedInfo)
return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-12 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
)
else return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div class='col-2 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
);
}
}
1st of all - keep structure/tree unmodified [and more maintainable]:
render() {
return (
<div class='container-fluid'>
<div class='row no-gutters'>
{this.state.maximizedInfo &&
<div class='col-10'>
some other info that takes up more space ...
</div>
}
<div key="infoPanel" class='col-2 padding-0'>
<InformationPanel toggleInfoPanel={this.toggleInfoPanel}/>
</div>
</div>
</div>
);
}
Adding key prop helps in reconcilation - article
After this change <InformationPanel/> should not be rerendered. Notice that change is on parent - the place where child nodes differs. Parent not changed, props not changed, no rerendering.
2nd - above is not enough - we want size change!
I'd say that's a 'structural problem' - styling should be done inside <InformationPanel/> with required change passed as prop (f.e.):
<InformationPanel key="infoPanel" wide={!this.state.maximizedInfo} toggleInfoPanel={this.toggleInfoPanel}/>
// and in render in <InformationPanel/>
<div className={`padding-0 ${this.props.wide ? 'col-12' : 'col-2'}`}>
...
Still use key prop!
Other options for conditional styling in this thread
xadm's answer was correct that key is essential for tree reconciliation. The thing is, I discovered that the key needs to be present in the parent components, not necessarily in the InformationPanel component. The below code works:
if (this.state.maximizedInfo)
return (
<div key='a' class='container-fluid'>
<div key='b' class='row no-gutters'>
<div key='c' class='col-12 padding-0'>
{informationPanel}
</div>
</div>
</div>
)
else return (
<div key='a' class='container-fluid'>
<div key='b' class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div key='c' class='col-2 padding-0'>
{informationPanel}
</div>
</div>
</div>
);
Since you don't want to lose state in InformationPanel, you can declare it outside conditional rendering so that it won't be getting unmounted on state change. Code will look something like below:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maximizedInfo: false
};
this.toggleInfoPanel = this.toggleInfoPanel.bind(this);
}
toggleInfoPanel() {
this.setState({maximizedInfo: !this.state.maximizedInfo});
}
render() {
const informationPanel = <InformationPanel toggleInfoPanel={this.toggleInfoPanel} />
if (this.state.maximizedInfo)
return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-12 padding-0'>
{informationPanel}
</div>
</div>
</div>
)
else return (
<div class='container-fluid'>
<div class='row no-gutters'>
<div class='col-10'>
some other info that takes up more space ...
</div>
<div class='col-2 padding-0'>
{informationPanel}
</div>
</div>
</div>
);
}
}

How to change the state in conditional - React

I need to change the state of sibling components in my React App.
I use state and setstate
I need to change the state of sibling components. When loading the page, there must exist (visible in the page) <BkUser /> and when clicking "button id =" ds-visual "it must be deleted (<BkUser /> mustn't exist) and there must exist <BkDescanso />.
When you click on <BkSleep /> (in the div parent) you should remove <BkDescanso /> and show <BkUser />
This is the web.
There should never be <BkUser/> and <BkSleep> at the same time. <Bkuser /> is the blue block and <BkDescanso /> is the red block
This is my code:
Edit: I edit my original code because I fix the problem. This is the final OK Code. In the end the most important thing was the state conditional
{
this.state.usuario ? (<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>): (<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>)}
import React, { Component } from 'react';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
usuario: true,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
usuario: !state.usuario
}));
//alert("Works button");
}
render(){
return (
<header className="header">
<div className="configuracion">
{
this.state.usuario
? (
<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>
)
: (
<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>
)}
<div className="content-btn">
<button id="config" className='btn btn--rounded'><span className="ico-configuracion"></span></button>
<button id="salir" className='btn btn--rounded'><span className="ico-exit"></span></button>
</div>
</div>
</header>
);
}
}
class BkUser extends Component{
render(){
return ((
<div className='usuario'>
<img src="../img//usuario.svg" alt="Imagen usuario"/>
<div className="content-usuario">
<span id="nm-usuario" className="h4">Hermione Jane Granger</span>
<span id="tp-usuario" className="h5">Supervisor</span>
</div>
<div className="content-descansos">
<div className="botones">
<button id="ds-visual" className='btn btn--rounded' onClick={this.props.handleClick}><span className="ico-visual"></span></button>
<button id="ds-admin" className='btn btn--rounded'><span className="ico-tiempo-administrativo"></span></button>
<button id="ds-otros" className='btn btn--rounded'><span className="ico-descanso"></span></button>
</div>
<div className="ds-actual">
<span id="ds-tipo">Administrativo</span>
<span id="ds-tiempo">00:08:47</span>
</div>
</div>
</div>
));
}
}
class BkDescanso extends Component {
render(){
return ((
<div className='usuario descanso' onClick={this.props.handleClick}>
<h3>Finalizar descanso</h3>
</div>
));
}
}
export default Header;
Right now handleClick works but always exist BkUser and BkDescanso. I need only one to exist. If you click on id = "ds-visual" the bkUser block should disappear and BkDescanso appear. Then if you click on div className = 'user rest' in BkUser there should only be BkDescanso.
I think that it is not able to know when it is true and when it is false to show or hide
Thanks a lot for the help.
You're missing two things:
First you have to pass the handleClick function to the BkUser component, and then you have to call it via this.props.handleClick.
...
<BkUser handleClick={this.handleClick} usuario={this.state.usuario} />
....
<button
id="ds-visual"
className="btn btn--rounded"
onClick={this.props.handleClick}
>
ds-visual
<span className="ico-visual" />
</button>
CodeSandbox here.
Read more here.
You can change the state of the siblings by passing a function from the parent via props into them.
In the end your siblings are the children of their parent.
You can read this articles on how to change the state of child components.
React js change child component's state from parent component
https://medium.freecodecamp.org/react-changing-state-of-child-component-from-parent-8ab547436271
An other thing you could look into would be React Redux.

React router not rendering the component on click of a Array item

I'm creating a component which has array of items and showing them on one page using map function.
Need help on how I can show a detail view of a item by updating route value dynamically based on the item I clicked.
Currently when I click on a item know more though the url change to some wrong value I don't see any change in the page.
Component:
{location[area].map((item, index) =>
<div key={index} className="col-md-4 mt-3">
<div className="card">
<div className="card-body">
<h5 className="card-title">{item.title}</h5>
<p className="card-text">{item.description}</p>
<Link to={'/view/${item.title}'} onClick={() => addDetail(item)} className="btn btn-primary">Know more</Link>
</div>
</div>
</div>
)}
Router:
<Route path='/view/:id' exact component={DetailPage} />
DetailPage Component:
class DetailPage extends Component {
render() {
const selectedItem = this.props.selectedItem;
return(
<div>
<DetailedView selectedItem={this.props.selectedItem} />
</div>
);
}
}
Result Anchor Tag:
<a class="btn btn-primary" href="/view/${item.title}">Know more</a>
Onclick result url:
http://localhost:3000/view/$%7Bitem.title%7D
You need to use backticks for the to prop of your Link components so that template literals will be used and the variable will be inserted into the string.
<Link
to={`/view/${item.title}`}
onClick={() => addDetail(item)}
className="btn btn-primary"
>
Know more
</Link>
Well,As #tholle suggested use template literal.Your route seems fine, just make use of react life cycle method componentWillReceiveProps in the Detail Page Component.Here is the code
class DetailPage extends Component {
componentWillReceiveProps(nextProps){
if(this.props.match.params.id !== nextProps.match.params.id){
//make a call with updated nextProps.match.id or filter the existing data store
with nextProps.match.id
}
}
render() {
const selectedItem = this.props.selectedItem;
return(
<div>
<DetailedView selectedItem={this.props.selectedItem} />
</div>
);
}
}

React - Call parent method from within child component

Apologies for the noob question but I'm reasonably new to React. I've seen a few similar questions on here to what I'm stuck with but I believe my requirements are slightly different.
I'm creating an Accordion component which is the parent of child AccordionItem components. I have given each AccordionItem its own state to keep track on whether the item is in an open or closed state. This works well and I now have a working accordion where each item can open or close when you click on the item's title.
However I now want to add functionality where if an AccordionItem is already in an open state it will close when another AccordionItem is selected. I believe I will need a state array or object within my Accordion.js which keeps track of currently selected AccordionItems which will then be updated on each click.
I'm struggling to work out how to pass a parent method down to the child component though.
My App.js currently looks like the below:
class App extends Component {
render() {
return (
<Accordion>
<AccordionItem title="Question One Title" itemid="question1" openOnLoad onChange={this.updateSelectedItems}>
<p>here is some text 1</p>
</AccordionItem>
<AccordionItem title="Question Two Title" itemid="question2" onChange={this.updateSelectedItems}>
<p>here is some text 2</p>
</AccordionItem>
<AccordionItem title="Question Three Title" itemid="question3" onChange={this.updateSelectedItems}>
<p>here is some text 3</p>
</AccordionItem>
</Accordion>
);
}
}
All the examples I've seen online show the parent having a prop which passes a reference of the chosen parent method down to the child component. However because my AccordionItems are defined in App.js rather than Accordion.js this.updateSelectedItems doesn't exist in this file.
I don't want to move the AccordionItems into the Accordion because then every Accordion would have the same data.
Thoughts?
Here's something I threw together. Working JSFiddle here: https://jsfiddle.net/gkysmsqz/6/
Accordion should maintain the active index for which accordion section should be shown. You can clone each child and add some extra props to them before render (active and toggleSection in the example below):
class App extends React.Component {
render() {
return (
<Accordion>
<AccordionItem title="Question One Title" itemid="question1" openOnLoad onChange={this.updateSelectedItems}>
<p>here is some text 1</p>
</AccordionItem>
<AccordionItem title="Question Two Title" itemid="question2" onChange={this.updateSelectedItems}>
<p>here is some text 2</p>
</AccordionItem>
<AccordionItem title="Question Three Title" itemid="question3" onChange={this.updateSelectedItems}>
<p>here is some text 3</p>
</AccordionItem>
</Accordion>
);
}
}
class Accordion extends React.Component {
state = {
activeIndex: 0
};
toggleSection = index => {
this.setState({
activeIndex: index
});
};
render() {
const rows = this.props.children.map( (child, i) => {
return React.cloneElement(child, { active: i === this.state.activeIndex, toggleSection: this.toggleSection.bind(this, i) });
});
return (
<div>
{rows}
</div>
);
}
}
const AccordionItem = props => (
<div>
<span onClick={props.toggleSection}>{props.title}</span>
<div className={props.active ? 'active accordion-item' : 'accordion-item'}>
{props.children}
</div>
</div>
);
I think you might find helpful this article https://reactjs.org/docs/composition-vs-inheritance.html.
In your case you might do something like this inside your Accordion component's render method:
render() {
return (
<div>
{
this.props.children.map(
child => {
child.passedMethod = this.updateSelectedItems;
return child;
}
)
}
</div>
);
}
If Accordion is keeping track of the AccordionItem components, then you should just let Accordion handle the rendering of AccordionItems.
App.js
class App extends Component {
render() {
return
<Accordion />;
}
Accordion.js
updateItems = () => ...
render() {
return (
<div>
<AccordionItem title="Question One Title" itemid="question1" openOnLoad onChange={this.updateSelectedItems}>
<p>here is some text 1</p>
</AccordionItem>
<AccordionItem title="Question Two Title" itemid="question2" onChange={this.updateSelectedItems}>
<p>here is some text 2</p>
</AccordionItem>
<AccordionItem title="Question Three Title" itemid="question3" onChange={this.updateSelectedItems}>
<p>here is some text 3</p>
</AccordionItem>
</div>
);
}
Go by the Reactjs basics, maintain your state in Parent (Accordion) and pass that state to all child (AccordionItem). Also, you need to pass an event handler (which will update parent state), it will be called when any child (AccordionItem) is selected.

How to make onMouseLeave in React include the child context?

I am currently building a dropdown button to learn React. I have made two onMouseEnter and onMouseLeave events in the parent div to make it visible when hovering it disappear when not. The problem is that those events only stick to parent.
How to make onMouseLeave in React include the child context?
Or, how can I keep the state expand be true when hovering on children?
class DropDownButton extends React.Component {
constructor(){
super();
this.handleHoverOn = this.handleHoverOn.bind(this);
this.handleHoverOff = this.handleHoverOff.bind(this);
this.state = {expand: false};
}
handleHoverOn(){
if(this.props.hover){
this.setState({expand: true});
}
}
handleHoverOff(){
if(this.props.hover){
this.setState({expand: false});
}
}
render() {
return (
<div>
<div className={styles.listTitle}
onMouseEnter={this.handleHoverOn}
onMouseLeave={this.handleHoverOff}>
{this.props.name}
</div>
<div>
{React.Children.map(this.props.children, function(child){
return React.cloneElement(child, {className: 'child'});
})}
</div>
</div>
);
}
}
You have two different divs in your DOM that don't overlap; I'll split up render so it's more obvious:
render() {
return (
<div>
<div className={styles.listTitle}
onMouseEnter={this.handleHoverOn}
onMouseLeave={this.handleHoverOff}>
{this.props.name}
</div>
<div>
{React.Children.map(this.props.children, function(child){
return React.cloneElement(child, {className: 'child'});
})}
</div>
</div>
);
}
The div that has the onMouseLeave attached to it does not contain the children; so, when the mouse moves to hover on a child, it leaves the div and this.handleHoverOff is called.
You might consider using CSS to hide the children if they shouldn't be displayed, or conditionally rendering them:
render() {
return (
<div className={styles.listTitle}
onMouseEnter={this.handleHoverOn}
onMouseLeave={this.handleHoverOff}>
{this.props.name}
{this.state.expanded && this.renderChildren()}
</div>
);
},
renderChildren() {
return (
<div>
{React.Children.map(this.props.children, function(child){
return React.cloneElement(child, {className: 'child'});
})}
</div>
);
}
By using the on mouse leave instead of mouse out and blocking the event on the children I got it reliably working no matter how fast I move through my list items.
https://stackoverflow.com/a/18837002/3302764

Resources