So I'm trying to create a navigation for my single page app that will smooth scroll down to the section of the page.
I want to have my navigation at the top of the page with links that when a user clicks, will smooth scroll them down to the section of the page. I'd also like it so if the user goes directly to the link website.com/about for example, it will smooth scroll to the section the same way as if you clicked about on the navigation component.
I understand how the react-router-dom works for routing pages, but I'm confused on how to make it work for this particular purpose.
Could this be achieved with HashRouter?
Here's the code I currently have:
function Header() {
return (
<>
<Link to="/">Hero</Link>
<Link to="/">About</Link>
<Link to="/">Contact</Link>
</>
);
}
function Hero() {
return (
<section>
<h1>Hero Section</h1>
</section>
);
}
function About() {
return (
<section>
<h1>About Section</h1>
</section>
);
}
function Contact() {
return (
<section>
<h1>Contact Section</h1>
</section>
);
}
export default function App() {
return (
<div className="App">
<BrowserRouter>
<Header />
<Hero />
<About />
<Contact />
</BrowserRouter>
</div>
);
}
I'm also providing a CodeSandBox, forks are appretiated! :)
What you could do is use an anchor tag instead of Link from react-router-dom and have an id on the sections. when the anchor tag is clicked scroll to the corresponding section
<a
href="/"
onClick={e => {
let hero = document.getElementById("hero");
e.preventDefault(); // Stop Page Reloading
hero && hero.scrollIntoView();
}}
>
Hero
</a>
// Rest of Code
function Hero() {
return (
<section id="hero">
<h1>Hero Section</h1>
</section>
);
}
and to scroll to a section using a url path you would have to get the extract the path from url and scroll to the section that has that specific path as an id
useEffect(() => {
let url = window.location.href.split("/");
let target = url[url.length - 1].toLowerCase();
let element = document.getElementById(target);
element && element.scrollIntoView({ behavior: "smooth", block: "start"});
}, []);
CodeSandbox here
Hope This Helps
To allow the link to update in the address bar, use the answer from #Abdullah Abid but change the <a> tags to <link> and the href to to.
See my ammendment to Abdullah's Sandbox Here
Related
I have a page with an embedded iframe script I am using with the Next-script tag.
script-page.tsx
import Script from 'next/script';
import Layout from '~/common/layout/Layout';
const ScriptPage = () => {
return (
<Layout>
<div className="m-0 md:m-12">
<div id="embedded-pressroom" data-publisher="212312" />
<Script
type="text/javascript"
src="my-link"
/>
</div>
</Layout>
);
};
export default ScriptPage;
It is showing the content if I go directly into the href, i.e localhost:3000/p/script-page/ but it is not showing if I go into localhost:3000/p/home-page/ and navigate back to the script page.
This is my navbar:
import Link from 'next/link';
const Navbar = () => {
return (
<div className="px-6 md:pr-0 navbar-content">
<h2>Navigation</h2>
<ul>
<li>
<Link href="/home-page">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/script-page">
<a>Script</a>
</Link>
</li>
</ul>
</div>
);
};
export default Navbar;
What is happening, why do I have to refresh the script-page in order for the content to be shown when navigating internally with Next-Link. It shows if I directly go in to the script page, but if I do:
Script Page
Navigate to Home Page
Go back to Script Page
I get "[iFrameSizer][Host page: embedded-pressroom-iframe] [Window focus] IFrame(embedded-pressroom-iframe) not found" in the console.
What can I do to fix this?
I have onClick events in my html that calls a function to get the alt value of each list item (so getting the movie name). I want the next step to simply get the single value that the function got and insert it into a separate webpage. How would I go about doing it? I gave it a try but I'm still very new to react, so I'm aware this doesn't work.
My html:
<div class="now-showing">
<ul class="movie-items">
<li>
<img src="/images/movie-a.jpg" alt="Movie A">
<button>BOOK</button>
</li>
<li>
<img src="/images/movie-b.jpg" alt="Movie B">
<button>BOOK</button>
</li>
<li>
<img src="/images/movie-c.jpg" alt="Movie C">
<button>BOOK</button>
</li>
</ul>
</div>
Script:
// getting movie name for movie bookings
let filmTitle = [];
function getMovieName(e) {
let link = e.target;
let img = link.closest("li").querySelector("img");
let item = img.alt;
filmTitle.push(item);
};
// input movie title to schedule page
function AddTitle() {
return (
<div className="schedule-heading">
<h2>Step 1: Select screening for {filmTitle}</h2>
</div>
)
}
ReactDOM.render(<AddTitle />, document.querySelector('.screenings'));
So far on my second webpage, I'm only getting "Step 1: Select screening for". What changes or additions would I need to make?
there what's wrong with your code:
first: u can't make img alt that saved by function exist when page reloading. so use ReactRouter instead if u wants to move to another page without reloading.
and make everything in pure react.
otherwise the img alt need to be stored in cookie or your server side.
second: react won't rerender without calling useState hook. so for react filmTitle will always blank. or u can also store it in another library for managing react state data like redux or mobx.
note this code snippet doesnt run properly because stackoverflow blocked it.
so u need to run it in your own project.
const {
BrowserRouter, Routes, Route, Outlet,Link,useNavigate} = ReactRouterDOM
function MyMovie(props){
let navigate = useNavigate();
const [data,setData] = React.useState([
{imgsrc:"/images/movie-a.jpg",imgalt:"Movie A",href:"/bookticket"},
{imgsrc:"/images/movie-b.jpg",imgalt:"Movie B",href:"/bookticket"},
{imgsrc:"/images/movie-c.jpg",imgalt:"Movie C",href:"/bookticket"}])
const bookMovie=(v)=>{
props.setFilmTitle(v.imgalt)
navigate(v.href)
}
return <div className="now-showing">
<ul className="movie-items">
{data.map(v=>
<li key={v.imgalt}>
<img src={v.imgsrc} alt={v.imgalt}/>
<button onClick={()=>bookMovie(v)}>BOOK</button>
</li>)}
</ul>
</div>
}
// input movie title to schedule page
function AddTitle(props) {
return ( <div className = "schedule-heading" >
<h2 > Step 1: Select screening for {props.filmTitle} < /h2>
</div>
)
}
function Page(){
return <div>
Welcome!
<Outlet/>
</div>
}
function App(){
const [filmTitle,setFilmTitle] = React.useState([])
return <BrowserRouter>
<Routes>
<Route path="/" element={<Page />}>
<Route index element={ <MyMovie filmTitle={filmTitle} setFilmTitle={setFilmTitle}/>} />
<Route path="bookticket" element={ <AddTitle filmTitle={filmTitle}/>}/>
</Route>
</Routes>
</BrowserRouter>
}
ReactDOM.render( <App /> , document.querySelector('.screenings'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/history#5/umd/history.development.js" crossorigin></script>
<script src="https://unpkg.com/react-router#6/umd/react-router.development.js" crossorigin></script>
<script src="https://unpkg.com/react-router-dom#6/umd/react-router-dom.development.js" crossorigin></script>
<div class="screenings">
</div>
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 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
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>
);
}