I have a component that is rendered with a static ID called #mainView. I have a component that renders a button element lower in the page, and I want to scroll up to the #mainView element when the button is clicked. As far as I can tell, this is a very simple user experience that has been apart of the web standard for decades now and can be accomplished with a simple ... in vanilla HTML (and extended with scroll-behavior: smooth as an experimental feature).
I have spent the last hour trying to replicate this behavior in React, to no avail. Hashes in URLs are ignored, and Gatsby complains about an external link. I have tried #reach/router/navigate and Link. I have tried to use onClick={...}, and manually trying to override onclick does not work. None of these produce any behavior at all and the only approaches I can find on SO involve extending React.Component and getting inside render(), using another method, using refs, and all sorts of stuff that should 10,000% not be necessary for such simple UX.
Is there any way to get React to easily replicate what I want here? And, why might the React developers actively break traditional web functionality? All responses appreciated.
Probably not the beautiful way to do it, but you can try using element.scrollIntoView, in the example I'm using the id and document.getElementById but you can replace it with ref
const Element = ({id,text})=>(<div id={id} style={{width:"100%", height: "400px", border:"solid 1px black"}} >{text}</div>);
const Scroll = ({to})=><span onClick={()=>document.getElementById(to).scrollIntoView({behavior:"smooth"})} >Scroll</span>
const App = ()=><div>
<h1>MyApp</h1>
<Element id="el-1" text="Lorem Ipsum" />
<Element id="el-2" text="Lorem Ipsum" />
<Element id="el-3" text="Lorem Ipsum" />
<Scroll to="el-2"/>
</div>
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>
And yes, if it's not to a element, but to the top window.scrollTo({top:0,behavior:'smooth'}) should be enough.
I was using Gatsby, and the solution was to import gatsby-plugin-smoothscroll package and add to plugins (docs), then navigate with:
<button onClick={() => scrollTo('#mainView')}>My link</button>
class Scroll extends React.Component {
constructor(props) {
super(props);
this.myRef1 = React.createRef();
this.myRef2 = React.createRef();
this.myRef3 = React.createRef();
this.myRef4 = React.createRef();
}
scrollSmooth(e, scroll) {
if (scroll === "sec1") {
this.myRef1.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
} else if (scroll === "sec2") {
this.myRef2.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
} else if (scroll === "sec3") {
this.myRef3.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
} else if (scroll === "sec4") {
this.myRef4.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
}
}
render() {
return (
<div className="container">
<ul className="list-group">
<li className="list" onClick={e => this.scrollSmooth(e, "sec1")}>
section1
</li>
<li className="list" onClick={e => this.scrollSmooth(e, "sec2")}>
section2
</li>
<li className="list" onClick={e => this.scrollSmooth(e, "sec3")}>
section3
</li>
<li className="list" onClick={e => this.scrollSmooth(e, "sec4")}>
section4
</li>
</ul>
<div className="row">
<div className="section1" ref={this.myRef1}>
<p>Section 1</p>
</div>
<div className="section2" ref={this.myRef2}>
<p>Section 2</p>
</div>
<div className="section3" ref={this.myRef3}>
<p>Section 3</p>
</div>
<div className="section4" ref={this.myRef4}>
<p>Section 4</p>
</div>
</div>
</div>
);
}
}
export default Scroll;
Hi refer this component.
Working link click here
Related
The situation is a bit complicated:
inside a component called "LeftSectionHeader" I have a div, which when clicked must render a component;
the component to be rendered is called "ProfileMenu", and is basically a div that must be rendered on top of "LeftSectionHeader" itself and another div;
All these components are rendered inside another component called "Main".
The problem is that if I define the function inside "LeftSectionHeader", "ProfileMenu" will be rendered inside, while I need it to not only be rendered outside, but even cover it; that's why you'll see some boolean vars inside "Main", because that is the only way i could render it, but it still doesn't cover the other divs. I'll attach the code of each component and how the final result should look below.
LeftSctionHeader:
function LeftSectionHeader(){
return(
<div class="left-section-header">
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
The div belonging to the "crop" class is the one that must be clicked to render "ProfileMenu"
ProfileMenu:
function ProfileMenu(){
return(
<div class="profile-side-menu">
//A lot of boring stuff
</div>
);
}
There are some functions related to this component, but they are not important, so I didn't put them, just ignore it
Main:
var p=true;
var m=true;
function Main(){
return(
<div class="main">
<Header />
<div class="left-section">
{m ? <div><LeftSectionHeader /><LangMenu /></div> : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
Before clicking on the orange div
After clicking
This might help as guidline, hopefully!
function LeftSectionHeader({ onClick }){
return(
<div class="left-section-header" onClick={onClick}>
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
function Main(){
const [showProfile, setShowProfile] = useState(false);
return(
<div class="main">
<Header />
<div class="left-section">
{!showProfile ? (
<div>
<LeftSectionHeader onClick={() => setShowProfile(true)} />
<LangMenu />
</div>
) : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
The simplest solution might be to pass a handler into the header component to toggle the menu:
function App () {
const [showMenu, setShowMenu] = useState();
return (
<div>
<Header onMenuToggle={() => setShowMenu(!showMenu)} />
{ showMenu && <Menu /> }
</div>
)
}
function Header ({ onMenuToggle }) {
<div onClick={onMenuToggle}>...</div>
}
Caveat: This will cause the entire App component to re-render when the menu state changes. You can mitigate this by either
A) placing the menu state closer to where it's actually needed, like in the sidebar component instead of at the top, or
B) using a context or other orthogonal state store.
Another approach would be to leave the state handling in the LeftSectionHeader component and then use a React portal to render the menu elsewhere in the DOM.
I'm trying to display a div when the mouse is over another div element. I've managed to do so via onMouseEnter and onMouseLeave.
The issue here is that if you quickly move from one div to another (it's an array of divs that contain data about a product), the value of index[0] becomes true.
The way it works is that I have an array initialised to false when the mouse enters one of them, it becomes true and shows the div that I wanted. Once it leaves, it set it back to false.
this.state = {
isProductsHovering: new Array(this.props.currentProducts.length).fill(false)
};
handleMouseHover = (idx) => {
this.setState({
isProductsHovering: update(this.state.isProductsHovering, {
[idx]: { $set: !this.state.isProductsHovering[idx] }
})
})
}
render() {
return this.props.currentProducts.map((product, idx) => {
return <Fragment key={idx}>
<div className="product-grid-view col-6 col-md-4" >
<div
className=" product-holder"
onMouseEnter={this.handleMouseHover.bind(this, idx)}
onMouseLeave={this.handleMouseHover.bind(this, idx)}>
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
this.state.isProductsHovering[idx] &&
<div className="product-buttons">
<Link to={`products/${product.id}`} className="btn-detail" text="View Details" />
<Link to='#' className="btn-cart" icons={["icon-cart", "icon-plus"]} />
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<Stars values={product.averageRating} {...starsRating} />
<span className="product-review">{getLength(product.reviews)} review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">SR</span>
</div>
</div>
</div>
</Fragment>
})
}
Update
I've made a stackblitz project to reproduce the same issue as suggested:
https://stackblitz.com/edit/react-mouse-hover.
For everyone that wants to see what I mean. I've attached a photo of the issue. If you move the mouse over the two divs (up and down as quick as you can), this what happens:
mouse hover broken
For situation like this, I wouldn't rely on array and index to make it work. You are further complicating your handleMouseHover functions and the checking of isHovering.
A 'more React' way of dealing with this situation is simply make each Product a component itself. And this Product component will have its own state of isHovered and handleOnHover method, that way you create a more concise and reliable code without having to rely on array index at all:
App.js can be as simple as this:
class App extends Component {
render() {
return (
<div>
{
data.map(product =>
<Product product={product} />
)
}
</div>
)
}
}
A new Product.js:
import React from 'react'
import ReactHoverObserver from 'react-hover-observer';
export default class Product extends React.Component {
render() {
const { product } = this.props
return (
<ReactHoverObserver className="product-grid-view col-6 col-md-4">
{
({isHovering}) => (
<div className=" product-holder">
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
isHovering &&
<div className="product-buttons">
<button className="btn-detail">View Details</button>
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<span className="product-review">0 review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">Currency</span>
</div>
</div>
)
}
</ReactHoverObserver>
)
}
}
I have put the moficiation in Stackblitz: https://stackblitz.com/edit/react-mouse-hover-2cad4n
Liren's answer is good advice and will help simplify the code. One thing I also noticed is that occasionally the HoverObserver won't 'hear' an event, and since the hover enter and hover exit events are listening to the same event, then the display state for the button will become reversed (i.e., it will show when the mouse is NOT hovering and hide when the mouse hovers over the observer).
I would recommend getting rid of the ReactHoverObserver HOC and instead just listen for the onMouseOver for hover enter and onMouseLeave for hover exit. That way, even if the div doesn't register a hover enter or exit, it will easily reset because onMouseOver will toggle the display state to true and onMouseLeave will reliably set the button's display state to false.
See here for those events in the docs:
https://reactjs.org/docs/events.html#mouse-events
The way you trigger it (from array or from a component) is semantics , the real issue is that these events don't always fire.
I had the same issue , apparently the events of react are not that reliable.
In my case I could live in a situation where a tooltip does not close , but not in the situation where 2 tooltips are open. In order to solve my issue , I returned to good old dom manipulation - Every time a tooltip appeared , it made all the other ones invisible.
It looked something like this :
showTooltip() {
// Clear old tooltips, EventTooltip is the name of my tooltip class.
Array.from(document.getElementsByClassName('EventTooltip'))
.forEach(tooltip=>tooltip.style = 'display:none;')
this.setState({showTooltip: true})
}
hideTooltip() {
this.setState({showTooltip: false})
}
I have been going around the internet for a while with no hope at all.
I am using "collapse" bootstrap for toggling visibility of a div.
I need to control this toggling through react onClick. Bootstrap says can be accessed through "$('.collapse').collapse()" but for some reason i canot do this in react.....any suggestions other than importing jQuery or using react-bootstrap component ??
class Container extends Component {
render() {
return (
<div className="wrapper">
<div className="collapse in" id="collapseExample">
<h1>Hide me</h1>
</div>
<div className="container">
<div className="row">
<button
data-toggle="collapse"
data-target="#collapseExample"
aria-expanded="false"
aria-controls="collapseExample">
Get a random Book
</button>
</div>
</div>
</div>
)
}
Bootstrap javascript depends on jQuery. jQuery and React have different ways to manipulate the DOM. To avoid issues in the future, you shouldn't use both. If you are using React, thinking in React is important.
A good React wrapper of Bootstrap is reactstrap. I recommend you use the library to build Bootstrap-based component, not write the Bootstrap raw class. A component named Collapse might fit your need, as you can see in sample code here.
If you are not interested in importing any library at all, it's fairly simple to write the component by yourself, using state. The internal idea is similar to the Collapse sample code above:
class MyCollapse extends Component {
state = {
isOpen: false
};
toggleState = () => this.setState( prevState => ({ isOpen: !prevState.isOpen }));
render() {
return (
<div>
<button onClick={this.toggleState}>Click to toggle</button>
{
this.state.isOpen &&
<div className="my-collapse">
{/* COLLAPSE CONTENT */}
</div>
}
</div>
)
};
}
Every time you click the button, the state attribute isOpen is toggled, which leads to a re-render of the component. That's one of the biggest strength of React: very flexible.
The following snippet illustrates how it should be done. If its still not working correctly in your program i would verify the CDN script links are correct.
Reminder, in ReactJS we should never use jQuery we manipulate the virtual DOM instead via components.
// Example stateless functional component
const SFC = props => (
<div>{props.label}</div>
);
// Example class component
class Container extends React.Component {
render() {
return (
<div className="wrapper">
<div className="collapse" id="collapseExample">
<h1 className="card card-body">Hide me</h1>
</div>
<div className="container">
<div className="row">
<button className="btn btn-primary" type="button" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">Get a random book</button>
</div>
</div>
</div>
) }
}
// Render it
ReactDOM.render(
<Container/>,
document.getElementById("react")
);
<div id="react"></div>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></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>
I have more components in one page. All pages loaded at the same time.
But i want after scroll event loading next componentas.
HomePage.js:
render() {
return (
<section className="section1">
<Component1 />
</section> <section className="section2">
<Component2 />
</section>
<section className="section3">
<Component3 />
</section>
<section className="section4">
<Component4 />
</section>
<section className="section5">
<Component5 />
</section>
<footer>
<Footer />
</footer>
);
}
Have any idea? It is possible in react.
Thank you!
You'll need to listen to a scroll event -- something like this:
componentDidMount() {
window.addEventListener('scroll', this.handleScroll)
}
Keep in mind that you may want to listen to a scroll event on a specific element, and not the entire window. This will depend on your requirements for how/when these other components should load.
In handling that event, keep track of your scroll distance somehow.
handleScroll = (event) => {
this.setState({
scrollY: window.scrollY
});
}
Note: Updating your state on every scroll event is probably over-kill though. I think the best approach would be to detect when the user has scrolled to the bottom and then conditionally load/render new components.
Then, in your render function, conditionally render additional components based on the value of your scroll distance. Again, you will likely need to include more sophisticated logic here based on your specific needs, but here is a rudimentary example based on scroll distance:
render() {
let additionalComponents = null;
if (this.state.scrollY > 1000) { //arbitrary amount
additionalComponents = (
<Component6 />
);
}
return (
<section className="section1">
<Component1 />
</section> <section className="section2">
<Component2 />
</section>
<section className="section3">
<Component3 />
</section>
<section className="section4">
<Component4 />
</section>
<section className="section5">
<Component5 />
</section>
{ additionalComponents }
<footer>
<Footer />
</footer>
);
}
I apologize for the simple question, I'm new to react and I have yet to find a solution for this problem. When clicked, I'm trying to retrieve the value for each button. The value repeatedly comes back as undefined. What am I doing wrong? I greatly appreciate your help!
class BottomListFilter extends Component {
constructor(props) {
super(props);
this.handleButton = this.handleButton.bind(this);
}
handleButton = (evt) => {
console.log(evt.target.value);
};
render() {
const listFilter = this.props.initialAdvisorList.map((filter) =>
<li key={filter.id}>
{filter.location}
</li>
);
return(
<div>
<div>
<button
value='location'
onClick={this.handleButton}
>
<h2>Choose by location</h2>
</button>
<button
value='state'
onClick={this.handleButton}
>
<h2>Choose by state</h2>
</button>
<button
value='practice'
onClick={this.handleButton}
>
<h2>Choose by practice</h2>
</button>
<button
value='topic'
onClick={this.handleButton}
>
<h2>Choose by topic</h2>
</button>
</div>
<div>
<ul>
{listFilter}
</ul>
</div>
</div>
);
}
}
You can certainly do evt.target.parentNode.value and it will work in this case. It may be just me but I find that solution against the spirit of React as it's reminisce of traversing the DOM (JQuery days).
Passing the value to handleButton function is much cleaner and more flexible:
<button onClick={this.handleButton.bind(null, 'location')}>
<h2>Choose by location</h2>
</button>
Now your handleButton looks like this:
handleButton(value, event) {
console.log(value);
console.log(event);
}
In this case, you don't really need "event" anymore and you could easily do handleButton(value) but I have included it just for the sake of clarity.
you can simplify your handleButton situation. it doesn't need to be a method. (in the code below, i also stripped away the ul, just to isolate the issue.)
i don't think this is what was causing the problem, though. most likely, the problem was not a React issue but just a simple DOM issue. event.target means "the lowest-level node where the event took place". if you clicked on the h2, that node is the h2, which has no value property.
just don't use an h2. put the text directly inside the button. also shown in the code sample.
var handleButton = evt => {
console.log(evt.target.value)
}
class BottomListFilter extends Component {
constructor(props) {
super(props);
this.handleButton = this.handleButton.bind(this);
}
render() {
return(
<div>
<button
value='location'
onClick={handleButton}
>
Choose by location
</button>
<button
value='state'
onClick={handleButton}
>
Choose by state
</button>
<button
value='practice'
onClick={handleButton}
>
Choose by practice
</button>
<button
value='topic'
onClick={handleButton}
>
Choose by topic
</button>
</div>
);
}
}