How to make onMouseLeave in React include the child context? - reactjs

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

Related

Focus on (scroll to) a DIV element in React

I have a toggle DIV element and I want to focus on (scroll to) it when it opens.
I followed the doc together with tabindex="-1" with no effect.
class App extends Component {
constructor(props) {
super(props);
this.state = {
formVisible: false
}
this.formRef = React.createRef();
this.onReply = this.onReply.bind(this);
}
onReply(e) {
this.formRef.current.focus(); // doesn't have any effect
this.setState({formVisible: true});
}
render() {
return (
<div>
<div>
<a onClick={this.onReply} href="#">Reply</a>
</div>
<div>
....
</div>
<div ref={this.formRef} tabindex="-1">
{this.state.formVisible &&
<form>
...
</form>
}
</div>
</div>
);
}
}
So far I'm using an anchor, but I don't find this solution very elegant...
...
<a onClick={this.onReply} href="#form">Reply</a>
...
<a name="form"></a>
{this.state.formVisible &&
...
Another problem of the anchor approach is, when there are more dynamically created elements on the page. Then I have to use some kind of ID for the anchor, which is pretty complex:
const {id} = this.props.data;
...
<a onClick={this.onReply} href={"#form-" + id}>Reply</a>
...
<a name={"form-" + id}></a>
{this.state.formVisible &&
...
Because your form is rendered only when formVisible is true, you need to wait until your component is re-rendered before trying to scroll to it. Use componentDidUpdate combined with scrollIntoView:
componentDidUpdate(prevProps) {
// `formVisible` went from false -> true, scroll the <form> into view
if (!prevProps.formVisible && this.props.formVisible) {
this.formRef.current.scrollIntoView();
}
}

Is there any obvious reason this won't render?

I'm pulling in an array of objects and mapping them to another component to be rendered.
renderRatings(){
if(this.props.ratings.length > 0){
return this.props.ratings.map(rating => {
<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>
});
}
}
This is where I render the rendering function.
render() {
return (
<div>
{this.renderRatings()}
</div>
);
}
}
This is the component I'm trying to populate and have rendered.
class Rating extends Component{
componentDidMount(){
console.log("props equal:", this.props)
}
render() {
return (
<div className="card darken-1" key={this.props._id}>
<div className="card-content">
<span className="card-title">{this.props.title}</span>
<p>{this.props.value}</p>
<button>Edit</button>
<button onClick={() => this.deleteRating(this.props._id)}>Delete</button>
</div>
</div>
);
}
}
export default connect({ deleteRating })(Rating);
No errors are being thrown, but when the page loads, the surrounding menu comes up, and the fetch request returns an array and supposedly maps it to the 'Rating' component, but no mapped Rating cards appear.
in your map, you're not returning the Rating etc... because you used { to define a code block, you have to type return. And since it's multi-line, use parens to mark the start and end of the Rating component.
return this.props.ratings.map(rating => {
<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>
needs to be
return this.props.ratings.map(rating => {
return (<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>)

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

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.

Calling a function for ALL child components

I have 3 components. They parent layout, a select box, and a panel this is generated x times from some data.
<Layout>
<Dropdown>
<Panel>
<Panel>
<Panel>
I'm trying to make it so when the select value changes, the contents of each panel changes. The changes are made by doing some math between the new select value, and data that is stored in the panel component. Each panel has different data.
Layout.js
updateTrueCost(selected){
this.refs.panel.setTrueCost
}
render(){
return (
<div>
<div class="row">
Show me the true cost in
<CurrencyDrop currencyChange = {(e) => this.updateTrueCost(e)} data = {this.state.data} />
</div>
<div class="row">
{this.state.data.map((item, index) => (
<Panel ref="panel" key = {index} paneldata= {item} />
))}
</div>
</div>
);
}
Panel.js
setTrueCost(selected){
//Do some math
this.setState({truecost: mathresult})
}
render(){
return(
<div>
{this.state.truecost}
</div>
)
}
CurrencyDrop.js
onHandelChange(e){
this.props.currencyChange(e);
}
render(){
return(
<Select
onChange={this.onHandelChange.bind(this)}
options={options} />
)
}
The current result is only the last panel updates when the select changes. I'm guessing I'm doing something wrong with the ref handling, but I must not be searching the right terms because I can't find any related questions.
Instead of calling ref's method use React build-in lifecycle methods.
class Panel extends React.Component {
componentWillReceiveProps (newProps) {
// compare old and new data
// make some magic if data updates
if (this.props.panelData !== newProps.panelData) {
this.setState({trueCost: someMath()});
}
}
render () {
return <div>{this.state.trueCost}</div>
}
}
Then just change input props and all data will be updated automatically.

User react router inside of map

I would like to use react-router (must use 2.8.1) for rendering content inside a list (using map).
However, if I display {this.props.children} outside the .map, it renders one at time.
I need it to display inline/under the list entry.
How can I achieve this?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies
};
}
render() {
return (
<div>
<h2>Repos</h2>
{x.movies.map(movie =>
<span key={movie.id}>
{movie.name}
<NavLink to={"/repos/" + movie.id + "/next"}>React Router</NavLink>
</span>
)}
{this.props.children}
</div>
);
}
}
You are rendering the children inside the loop, which i believe is causing the extra Next Id:4 to be displayed between each entry.
Try the below, by rendering the children outside the loop.
{
x.movies.map(movie => (
<span>
<Result key={movie.id} result= {movie}/>
</span>
))
}
<span>{this.props.children}</span>

Resources