4

I have a nav bar function componenent which passes the name of the link to the parent component through onclick callback and its further passed to the main app component. The app component have an object with the name of the link and associated ref. onclick callback in the app component takes the ref from the object based on the link-name passed to it by the underlying component and invokes a window.scrollTo. window.scrollTo works when you click a link for the first time and when the page scrolls if another link is clicked from the sticky navbar, window doesn't scroll again but rather goes back to (0,0) and from there clicking the same link works.

//Call back in the App component.

manageContent(link){

   console.log(this.linksData[link].current)

    window.scrollTo({
        top: this.linksData[link].current.offsetTop,
        left: 0,
        behavior: 'smooth'
     })    
}

The above function is passed to Header component

<Header links={data} onclick={this.manageContent} ref={this.headerRef}/>

and in the header links are created using a NavLink function component where onClick would return the link-name back.

What i'm doing wrong, why does scrollTo work on second click once the page scroll to the top but not from the middle of the page or from a scrolled location.

I tried other scroll function as well and wierdly only scrollTo scrolls and that to with scrollOptions, scrollToView, moveTo etc didn't work at all.

I printed out the offsetTop in console and triggering window.scrollTo(0,"offsetTop printed in console"), works fine with no issues.

Here is the code.

App.js

class App extends React.Component {

  constructor(props){
    super(props);
    this.manageContent = this.manageContent.bind(this)

    this.state={}
    this.sectionRef1 = React.createRef();
    this.sectionRef2 = React.createRef();
    this.sectionRef3 = React.createRef();
    this.sectionRef4 = React.createRef();
    this.sectionRef5 = React.createRef();    
    this.headerRef = React.createRef();    
    this.heroRef = React.createRef();
  }
  manageContent(key){
    console.log(key)
    this.setState({key:key});
  }

setActivePage = (key) => {
    let refList = [this.sectionRef1,this.sectionRef2,this.sectionRef3,this.sectionRef4,this.sectionRef5]
    console.log(key)
    console.log(refList[key].current)
    if (refList[key].current){
        window.scrollTo({behavior: "smooth",top: refList[key].current.offsetTop})

    }
}
componentDidUpdate(prevProps, prevState) {  
  console.log("comp updated")
  this.setActivePage(this.state.key)
}
/*
componentDidMount(){
  window.addEventListener('scroll', this.scrollListener)
}
componentWillUnmount() {
  window.removeEventListener('scroll', this.scrollListener)
}    
*/
  render(){     
    return (
      <div className="bp-container-full bp-typography" key="app">
        <Header links={data.links} onclick={this.manageContent} ref={this.headerRef}/>
        <main key="main">
            <HeroSection ref={this.heroRef}/>
            <div className="bp-main">
            <section key="home"className="page" ref={this.sectionRef1}>
              <Home/>
            </section>
            <section key="aboutme" className="page" ref={this.sectionRef2}>
              <AboutMe/>
            </section>
            <section key="sitedetails" className="page" ref={this.sectionRef4}>  
              <SiteDetails/>
            </section>
            <section key="contact" className="page" ref={this.sectionRef5}>  
              <ContactForm/>
            </section>
            </div>
        </main>
        <Footer/>  
      </div>
    );
  }

}
export default App;

Header.js

class Header extends React.Component {
    constructor(props) {
        super(props);
        console.log(props)
        this.linkRef = React.createRef()
        this.state = {isOpen:false};
        this.headerRef = React.createRef()
    }

    render() {      
        const navlink = data.links.map((link,key)=>{ 
            return(
            <a href="#" key={link} ref={this.linkRef}
                    className="nav-list-item bp-upper"
                    onClick={() => this.props.onclick(key)}>
                  {link}
            </a>
        )})
        return (
            <header key="header-key" className={classnames("bp-header","bp-header-fixed",
                            {"is-scrolled":this.state.scrolled})} ref={this.headerRef}>
                <button className={classnames("bp-mobile-menu",{"is-open":this.state.isOpen})} onClick={()=>{this.setState({isOpen:!this.state.isOpen})}}>
                  <i className={classnames("fas", {"fa-bars":!this.state.isOpen, "fa-times":this.state.isOpen})}></i>
                </button>                            
                <div className={classnames("nav", "nav-align-centre",{"is-open":this.state.isOpen})}>
                    <nav className="nav-list nav-primary">
                        {navlink}
                    </nav>
                </div>
            </header>
        )
    }
}
export default Header;
bp82
  • 49
  • 1
  • 5

3 Answers3

1

Finally after almost writing the whole application multiple time found the issue.

My link had an href, yes silly me, taking the href="#" resolves the problem and probably explains as well why it worked in two clicks and not 1.

<a href="#" key={link} ref={this.linkRef}
                    className="nav-list-item bp-upper"
                    onClick={() => this.props.onclick(key)}>
                  {link}
            </a>
bp82
  • 49
  • 1
  • 5
0

Based on the code you provided, it's unclear what could be wrong. My hypothesis is that there is something wrong with the way you are assigning refs or handling the call-back.

Here's a working sandbox that will show you all the code you might possibly need to get this to work: https://codesandbox.io/s/navbar-click-scroll-into-section-us8y7

Essentially has the same layout as your App. We have a Navbar/Header with links and when one is clicked, we trigger a call-back and find the associated ref. Then scroll to the section assigned with that ref.

App.js

import React from "react";
import ReactDOM from "react-dom";
import Header from "./Header";
import HowItWorks from "./HowItWorks";
import BrowserCatalogue from "./BrowserCatalogue";
import Contact from "./Contact";
import Woof from "./Woof";

import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selected: null
    };
  }
  //refs
  howItWorks = React.createRef();
  browserCatalogue = React.createRef();
  contact = React.createRef();
  woof = React.createRef();

  changeSelection = index => {
    this.setState({
      selected: index
    });
  };

  componentDidUpdate(prevProps, prevState) {
    this.scrollToSection(this.state.selected);
  }

  scrollToSection = index => {
    let refs = [
      this.howItWorks,
      this.browserCatalogue,
      this.contact,
      this.woof
    ];

    if (refs[index].current) {
      refs[index].current.scrollIntoView({
        behavior: "smooth",
        nearest: "block"
      });
    }
  };

  render() {
    return (
      <div className="App">
        <div>
          <Header changeSelection={this.changeSelection} />
        </div>
        <div ref={this.howItWorks}>
          <HowItWorks />
        </div>
        <div ref={this.browserCatalogue}>
          <BrowserCatalogue />
        </div>
        <div ref={this.contact}>
          <Contact />
        </div>
        <div ref={this.woof}>
          <Woof />
        </div>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Header.js

import React from "react";

const Header = props => {
  const { changeSelection } = props;
  return (
    <div
      style={{
        background: "green",
        height: "50px",
        width: "100%",
        position: "fixed",
        top: "0"
      }}
    >
      <span onClick={() => changeSelection(0)}>Working</span>{" "}
      <span onClick={() => changeSelection(1)}>Catalogue</span>{" "}
      <span onClick={() => changeSelection(2)}>Contact</span>{" "}
      <span onClick={() => changeSelection(3)}>Woof</span>
    </div>
  );
};

export default Header;
Chris Ngo
  • 15,460
  • 3
  • 23
  • 46
  • My code was/is very similar to yours. I Would clean my code and see if there is anything else happening. Thanks much appreciated for the quick response though. – bp82 Jul 25 '19 at 07:49
  • @bp82 you're welcome, its more or less the same idea. Let me know if there's anything else you want us to take a look at. With the code you've posted, its hard to deduce what could be wrong. – Chris Ngo Jul 25 '19 at 07:56
  • Posted the code now, still hasn't worked. Tried again with the scrollToView() and it does nothing, scrollTo atleast works after 2 clicks, first one takes back to top and the second then to the page. – bp82 Jul 25 '19 at 09:21
0

Just found this react ref with focus() doesn't work without setTimeout (my example) and yes that helps. setTimeOut indeed gives a sort of fix but also the post makes sense on what maybe wrong with my code.

bp82
  • 49
  • 1
  • 5