4
votes

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;
3
What is the value you pas to mangeContent(link) - Cool Guy
values passed to manageContent is name of the link e.g. "home","contact". The object in the App component is something like this {"home":homeRef,"contact":contactRef} - bp82
why not just this.linksData[link].current.scrollIntoView()? - John Ruddell
Have tried that as well, scrollIntoView() doesn't work at alll, from console though that works as well. - bp82

3 Answers

1
votes

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>
0
votes

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;
0
votes

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.