Reputation: 173
I have 7 components on a website page with a navbar on the top. The navbar have 7 titles that representing those 7 components. Each component has complicated data structures and flows. What I want to achieve is; when a user clicks on one of navbar's title, it will scroll down to the specific components.
My questions;
Example code:
const SectionOne = () => <div>Section One</div>
const SectionTwo = () => <div>Section Two</div>
const SectionThree = () => <div>Section Three</div>
const SectionFour = () => <div>Section Four</div>
const SectionFive = () => <div>Section Five</div>
const SectionSix = () => <div>Section Six</div>
const SectionSeven = () => <div>Section Seven</div>
const titlesArr = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"]
const nav = () => {
return (
<>
{
titlesArr.map((i) =>
<p>{i}</p>
}
</>
);
}
const App = () => {
return (
<Nav />
<SectionOne />
<SectionTwo />
<SectionThree />
<SectionFour />
<SectionFive />
<SectionSix />
<SectionSeven />
);
}
<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>
Upvotes: 0
Views: 1162
Reputation: 1074989
You can do this with React, but you might want to consider giving the elements id
values and then just using <a href="#id">...</a>
to scroll to them using the browser's built-in handling. That also has the advantage of adding a history entry and providing a hash fragment in the URL that the use can bookmark.
If for some reason you don't want to do that, you can use seven useRef
calls, either by repeating them in the code or by using a loop, to get seven refs. Then you can put them on the seven elements and, when a relevant navigation element is clicked, use the ref that relates to to scroll the element into view (for instance, using scrollInView
):
const { useRef } = React;
const NavBar = ({sections}) => {
const scrollToSection = (event, ref) => {
event.preventDefault();
ref.current.scrollIntoView({
behavior: "smooth",
});
};
return <nav>
{sections.map(({title, ref}) =>
<a key={title} href={`#${title}`} onClick={(event) => scrollToSection(event, ref)}>
{title}
</a>
)}
</nav>;
};
const sectionTitles = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
];
const Example = () => {
const sections = sectionTitles.map((title) => {
return {title, ref: useRef(null)};
});
return <div>
<NavBar sections={sections} />
<main>
{sections.map(({title, ref}) =>
<section key={title} ref={ref}>
{title}
</section>
)}
</main>
</div>;
};
ReactDOM.createRoot(document.getElementById("root"))
.render(<Example />);
section {
min-height: 20rem;
border: 1px solid black;
}
nav a {
padding-left: 4px;
padding-right: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
You've edited your question to show an MRE of what you currently have (although App
has an error, you can't have adjacent JSX elements without some kind of wrapper). I would take those individual SectionOne
, SectionTwo
, etc. variables and put them in an array (not unlike the above), but keep reading if you don't want to do that. You'll also need to wrap your sections in a call to forwardRef
and accept the ref
parameter (details here):
const { useRef, forwardRef } = React;
// Since you'll need *something* that relates titles to sections,
// and you already had an array of titls, it seems reasonable to use
// an array rather than individual `SectionOne`, `SectionTwo`, etc.
// constants for that. But you can have them and then repeat them below
// for the `Section` values if you prefer.
const sections = [
{title: "One", Section: forwardRef((_, ref) => <div ref={ref}>Section One</div>)},
{title: "Two", Section: forwardRef((_, ref) => <div ref={ref}>Section Two</div>)},
{title: "Three", Section: forwardRef((_, ref) => <div ref={ref}>Section Three</div>)},
{title: "Four", Section: forwardRef((_, ref) => <div ref={ref}>Section Four</div>)},
{title: "Five", Section: forwardRef((_, ref) => <div ref={ref}>Section Five</div>)},
{title: "Six", Section: forwardRef((_, ref) => <div ref={ref}>Section Six</div>)},
{title: "Seven", Section: forwardRef((_, ref) => <div ref={ref}>Section Seven</div>)},
];
// If you need the `titlesArr` for something:
const titlesArr = sections.map(({title}) => title);
const Nav = ({sections, refs}) => {
const scrollTo = (ref) => {
ref.current.scrollIntoView({behavior: "smooth"});
};
return sections.map(({title, ref}) =>
<p key={title} onClick={() => scrollTo(ref)}>{title}</p>
);
};
const App = () => {
const sectionsWithRefs = sections.map(section => ({
...section,
ref: useRef(null),
}));
return <div>
<Nav sections={sectionsWithRefs} />
{sectionsWithRefs.map(({title, Section, ref}) => <Section key={title} ref={ref} />)}
{/* Just for the example, a spacer so we have room to scroll down to sections */}
<div style={{height: "50rem"}} />
</div>;
};
ReactDOM.createRoot(document.getElementById("root"))
.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you really want those individual SectionOne
, SectionTwo
, etc.:
const { useRef, forwardRef } = React;
const SectionOne = forwardRef((_, ref) => <div ref={ref}>Section One</div>);
const SectionTwo = forwardRef((_, ref) => <div ref={ref}>Section Two</div>);
const SectionThree = forwardRef((_, ref) => <div ref={ref}>Section Three</div>);
const SectionFour = forwardRef((_, ref) => <div ref={ref}>Section Four</div>);
const SectionFive = forwardRef((_, ref) => <div ref={ref}>Section Five</div>);
const SectionSix = forwardRef((_, ref) => <div ref={ref}>Section Six</div>);
const SectionSeven = forwardRef((_, ref) => <div ref={ref}>Section Seven</div>);
// Since you'll need *something* that relates titles to sections,
// and you already had an array of titls, it seems reasonable to use
// an array rather than individual `SectionOne`, `SectionTwo`, etc.
// constants for that. But you can have them and then repeat them below
// for the `Section` values if you prefer.
const sections = [
{title: "One", Section: SectionOne},
{title: "Two", Section: SectionTwo},
{title: "Three", Section: SectionThree},
{title: "Four", Section: SectionFour},
{title: "Five", Section: SectionFive},
{title: "Six", Section: SectionSix},
{title: "Seven", Section: SectionSeven},
];
// If you need the `titlesArr` for something:
const titlesArr = sections.map(({title}) => title);
const Nav = ({sections, refs}) => {
const scrollTo = (ref) => {
ref.current.scrollIntoView({behavior: "smooth"});
};
return sections.map(({title, ref}) =>
<p key={title} onClick={() => scrollTo(ref)}>{title}</p>
);
};
const App = () => {
const sectionsWithRefs = sections.map(section => ({
...section,
ref: useRef(null),
}));
let n = 0;
return <div>
<Nav sections={sectionsWithRefs} />
{/* I would use `sectionsWithRefs` as in the previous example, but
if you _really_ don't want to: */}
<SectionOne ref={sectionsWithRefs[n++].ref} />
<SectionTwo ref={sectionsWithRefs[n++].ref} />
<SectionThree ref={sectionsWithRefs[n++].ref} />
<SectionFour ref={sectionsWithRefs[n++].ref} />
<SectionFive ref={sectionsWithRefs[n++].ref} />
<SectionSix ref={sectionsWithRefs[n++].ref} />
<SectionSeven ref={sectionsWithRefs[n++].ref} />
{/* Just for the example, a spacer so we have room to scroll down to sections */}
<div style={{height: "50rem"}} />
</div>;
};
ReactDOM.createRoot(document.getElementById("root"))
.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Upvotes: 2