React change view and then scroll to element - reactjs

I want to link from one view to another and then scroll to a specific element. I'm not intrested in any animations, only want to have the element in view. The link from one view to another is done through react router.
I guess I could somehow create references on the elements I want to scroll to and pass them to the other view, but don't know if that's the correct approach?
A simple example. (Not working, but hopefully you understand what I want to achieve)
const ViewOne = () => {
const navigate = useNavigate(); // From react-router v6
return (
<p onClick={() =>
{
navigate("ViewTwo");
// What more do I have to add?
}}>
Link to section two, in view two
</p>
);
}
export default ViewOne;
const ViewTwo = () => {
return (
<>
<section style={{height: "100vh"}}></section>
<section style={{height: "100vh"}}>
Scroll here?
</section>
<section style={{height: "100vh"}}></section>
</>);
}
export default ViewTwo;
I'm using react-router-dom-v6

Give the sections you want to target and scroll to id attributes. Pass a target id in route state. Use a useEffect hook to target the element and scroll it into view.
Example:
const ViewOne = () => {
const navigate = useNavigate(); // From react-router v6
return (
<p
onClick={() => {
navigate("/viewtwo", { state: { targetId: "section2" } });
}}
>
Link to section two, in view two
</p>
);
};
...
const ViewTwo = () => {
const { state } = useLocation();
const { targetId } = state || {};
useEffect(() => {
const el = document.getElementById(targetId);
if (el) {
el.scrollIntoView();
}
}, [targetId]);
return (
<>
<section id="section1" style={{ height: "100vh" }}></section>
<section id="section2" style={{ height: "100vh" }}>
Scroll here?
</section>
<section id="section3" style={{ height: "100vh" }}></section>
</>
);
};
...
<Router>
<Routes>
...
<Route path="/viewone" element={<ViewOne />} />
<Route path="/viewtwo" element={<ViewTwo />} />
...
</Routes>
</Router>

you can use "useRef" to scroll to that position with click event or try useEffect for scroll to that position after component rendered.
const ViewTwo = () => {
const scroller = useRef(null)
const executeScroll = () => scroller.current.scrollIntoView()
return (
<>
<section style={{height: "100vh"}}></section>
<section ref={scroller} style={{height: "100vh"}}>
Scroll here?
</section>
<button onClick={executeScroll}> Click to scroll </button>
<section style={{height: "100vh"}}></section>
</>);
}
export default ViewTwo;

Related

Adding component to array of components without re-rendering the existing components? [Next.js]

I have a dynamic array (state) of React components – and each components has an entry-animation on mount. But every time a component is added to the array all the components re-renders – which also triggers the entry-animation for all components...
My parent page looks something like this:
export default function Home({ projects }) {
const [components, setComponents] = useState([]);
const createComponent = (project) => {
const id = uniqid();
const temp = <Project block={project} key={id} />;
setOpenBlocks((prevState) => [temp, ...prevState]);
};
return (
<>
//Small images / Create component on click:
<div>
{projects.map((project, i) =>
<div key={project.page.id}>
<Image src alt
onClick={() => createComponent(project)}
/>
</div>
})}
</div>
//Big images / render array of components:
<div>
{components &&
components.map((block, i) => <Fragment key={i}>{component}</Fragment>)}
</div>
</>
);
}
And my 'Project' (child) component looks like this:
export default function Project({ project }) {
const [loaded, setLoaded] = useState(0);
return (
<AnimatePresence>
{project && (
<motion.figure
initial={{ width: 0 }}
animate={{ width: "100%" }}
style={{ opacity: loaded }}
>
<img
onLoad={() => setLoaded(1)}
/>
</motion.figure>
)}
</AnimatePresence>
)
}
So the entry-animation is made via the framer-motion AnimatePresence component, as well as the onLoad function changing opacity from 0 to 1. Both of them re-triggers when updating the array. And I need only the component that was just added to animate!
The child components props will not change once it is rendered.
I've tried wrapping the child component in 'memo', and tried updating the array with useCallback. Inserting the array like this somehow seemed to work (but I don't think it should?):
<div>
{components}
</div>
All input is welcome, thanks!

Using refs and .reduce scroll to the id of selected element with react with useState

sorry if the title doesn't make much sense.
I've been refactoring my code from this.state to useState, and I finally got things working except for the refs...
In my original code I was making individual axios calls and using this.state along with this refs code:
const refs = response.data.reduce((acc, value) => {
acc[value.id] = createRef();
return acc;
}, {});
but now I refactored my axios call to .all:
const getData = () => {
const getSunSigns = axios.get(sunSignAPI);
const getDecans = axios.get(decanAPI);
const getNums = axios.get(numbersAPI);
axios.all([getSunSigns, getDecans, getNums, refs]).then(
axios.spread((...allData) => {
const allSunSigns = allData[0].data;
const getAllDecans = allData[1].data;
const getAllNums = allData[2].data;
setSunSigns(allSunSigns);
setDecanSign(getAllDecans);
setNumerology(getAllNums);
})
);
};
useEffect(() => {
getData();
}, []);
so the response.data.reduce doesn't work cuz I'm not using 'response'.
I've tried several things but none worked.. unfortunately I deleted all the previous code but this is what I currently have, which works but obviously only takes one api:
const refs = sunSigns.reduce((acc, value) => {
acc[value.id] = createRef();
return acc;
}, {});
onClick = (id) => {
refs[id].current.scrollIntoView({
behavior: "smooth",
});
};
from the research I've done and the code I've tried I'm sure I'd have to map through the apis and then maybe use the reduce(???).. but I'm really not entirely sure how to go about it or how to rephrase my google search to get more accurate results.
what I'm trying to do specifically: on certain pages an extra nav bar appears with the symbol of a specific sign/number. the user can click on one and it'll scroll to that specific one. I'm going to have several pages with this kind of feature so I need to dynamically set refs for each api.
any help or guidance will be highly appreciated!!
edit**
the above codes are in my Main component and this is where I'm setting the refs:
return (
<div className='main'>
<div className='main__side-container'>
<SideBar />
<div className='main__card-container'>
<Card
sunSigns={sunSigns}
numerology={numerology}
decanSign={decanSign}
refs={refs}
/>
</div>
</div>
<div className='main__bottom-container'>
<BottomBar
sunSigns={sunSigns}
numerology={numerology}
onClick={onClick}
/>
</div>
</div>
);
}
then this is the card:
export default function Card({ sunSigns, decanSign, refs, numerology }) {
return (
<>
<div className='card'>
<Switch>
<Route path='/astrology/western/zodiac'
render={(routerProps) => <Zodiac refs={refs} sunSigns={sunSigns} />}
/>
<Route path='/numerology/pythagorean/numbers'
render={(routerProps) => <NumberPage refs={refs} numerology={numerology} />}
/>
</Switch>
</div>
</>
);
}
and then this is the Zodiac page:
export default function Zodiac({ sunSigns, refs }) {
return (
<>
<div className='zodiac__container'>
<TitleBar text='ZODIAC :' />
<div className='card-inset'>
<div className='container-scroll'>
<SunSignsList sunSigns={sunSigns} refs={refs} />
</div>
</div>
</div>
</>
);
}
and the SunSignsList component:
export default function SunSignsList({ sunSigns, refs }) {
return (
<>
<div className='sunsignsitemlist'>
<ul>
{sunSigns.map(sign => {
return (
<SunSigns
refs={refs}
key={sign.id}
id={sign.id}
sign={sign.sign}
/>
);
})}
</ul>
</div>
</>
);
}
and the SunSigns component:
export default function SunSigns({
id,
sign,
decans,
refs
}) {
return (
<li ref={refs[id]}>
<section className='sunsigns'>
<div className='sunsigns__container'>
<div className='sunsigns__header'>
<h3 className='sunsigns__title'>
{sign}
{decans}
</h3>
<h4 className='sunsigns__symbol'>{symbol}</h4>
</section>
</li>
);
}
the above code is where my ref code is currently accessing correctly. but the end goal is to use them throughout several pages and comps in the same manner.
You can create three different objects holding the ref data for each list or if the id is same you can generate a single object which holds all the list refs.
const generateAllRefsObj = (...args) => {
const genSingleListRefsObj = (acc, value) => {
acc[value.id] = createRef();
return acc;
}
return args.reduce((acc, arg) => ({ ...arg.reduce(genSingleListRefsObj, acc), ...acc }), {})
}
Usage
const allRefs = generateAllRefsObj(sunSigns,decanSign,numerology)

TypeError: Cannot read property '0' of undefined when building my react app

while building my react app for deployment, I am getting this error
TypeError: Cannot read property '0' of undefined
when I am rending on port3000 I did not see this error but only get it while building the app.
Can anyone assist to resolve this?
import { useState } from "react";
import styles from "./Tabs.module.css"
const Tabs = ({ children}) => {
const [activeTab, setActiveTab] = useState (children [0].props.label);
const handleClick =( e, newActiveTab ) => {
e.preventDefault();
setActiveTab(newActiveTab);
}
return (
<div>
<ul className= {styles.tabs}>
{children.map ((tab) => {
const label = tab.props.label;
return (
<li
className= {label == activeTab ? styles.current : ""}
key= {label}
>
<a href="#" onClick={(e) => handleClick (e, label)}>{label}
</a>
</li>
)
})}
</ul>
{children.map ((tabcontent1) => {
if (tabcontent1.props.label == activeTab)
return (
<div key= {tabcontent1.props.label} className= {styles.content}>{tabcontent1.props.children}
</div>
);
})}
</div>
);
}
export default Tabs ;
In next js, when you don't put export const getServerSideProps = () => {} in your page then that page is automatically subjected to static side rendering. On development mode, you may see a lightening symbol on bottom-right. Anyway you can read the docs on data-fetching on nextjs. However, your issue on this situation can be easily fixed by setting the children through useEffect.
// handle null on your active tab render function
const [activeTab, setActiveTab] = useState(null);
useEffect(() => {
if(children.length)
children[0].props.label
}, [children])
Another Code Sample:
*A simple change in code structure and the way you are trying to do. It's on react but kind of same in next as well *
import React from "react";
const Tabs = ({ tabsData }) => {
const [activeTabIndex, setActiveTabIndex] = React.useState(0);
const switchTabs = (index) => setActiveTabIndex(index);
return (
<div style={{ display: "flex", gap: 20, cursor: "pointer" }}>
{/* Here active tab is given a green color and non actives grey */}
{tabsData.map((x, i) => (
<div
key={i}
style={{ color: activeTabIndex === i ? "green" : "#bbb" }}
onClick={() => switchTabs(i)}
>
{x.label}
</div>
))}
{/* Show Active Tab Content */}
{tabsData[activeTabIndex].content}
</div>
);
};
export default function App() {
// You can place it inside tabs also in this case
// but lets say you have some states on this component
const tabsData = React.useMemo(() => {
return [
// content can be any component or React Element
{ label: "Profile", content: <p>Verify all Input</p> },
{ label: "Settings", content: <p>Settings Input</p> },
{ label: "Info", content: <p>INput info</p> }
];
}, []);
return (
<div className="App">
<Tabs tabsData={tabsData} />
</div>
);
}
and here is also a example sandbox https://codesandbox.io/s/serverless-night-ufqr5?file=/src/App.js:0-1219

React: How to prevent menu component from being remounted when switching pages

Let's say that we have a React app with two pages A and B using a shared menu component Menu.
Our app renders either page A or page B, like the example below:
const Menu = (props) => {
React.useEffect(()=>{
console.log("The menu remounted");
}, []);
return (
<div id="menu" className="has-scrollbar">
<button onClick={() => props.onClick('a')}>A</button>
<button onClick={() => props.onClick('b')}>B</button>
</div>
);
}
const PageA = (props) => {
const .. = useSomeHooksUsedByPageA();
return (
<div>
<Menu {...somePropsFromPageA} />
<div>Content of page A</div>
</div>
);
}
const PageB = (props) => (
const .. = useSomeHooksUsedByPageB();
<div>
<Menu {...somePropsFromPageB} />
<div>Content of page B</div>
</div>
);
const App = () => {
const [pageKey, setPageKey] = React.useState("a");
switch (pageKey)
{
case "a":
return <PageA key="1" onClick={setPageKey} />;
case "b":
return <PageB key="1" onClick={setPageKey} />;
}
return "true"
}
Now, every time we switch pages (from A to B, or B to A), the menu is remounted and a message is printed to the console.
Using this component hierarchy where the menu receives props from the page, is there any way to tell React not to remount the menu when we switch pages?
(A typical use-case could be that the menu has a scroll, and we want to keep the scroll position when navigating different pages.)
Help is greatly appreciated!
One potential solution for this problem is to move <Menu/> into the <App/> component, and render each page after the menu.
This provides a couple of benefits:
The Menu won't be re-rendered whenever the page changes.
The onClick function does not need to be passed through props on each page just to provide it to the <Menu/> component nested within.
const Menu = (props) => {
React.useEffect(() => {
console.log("The menu remounted");
}, []);
return (
<div id="menu" className="has-scrollbar">
<button onClick={() => props.onClick("a")}>A</button>
<button onClick={() => props.onClick("b")}>B</button>
</div>
);
};
const PageA = () => (
<div>
<div>Content of page A</div>
</div>
);
const PageB = () => (
<div>
<div>Content of page B</div>
</div>
);
const App = () => {
const [pageKey, setPageKey] = React.useState("a");
let page;
switch (pageKey) {
case "b":
page = <PageB key="2" />;
break;
default:
page = <PageA key="3" />;
break;
}
return (
<>
<Menu onClick={setPageKey} />
{page}
</>
);
};
ReactDOM.render(<App />, document.querySelector("#app"))
Edit
Further to #glingt's comment regarding the hierarchy and how this needs to function, Context might be a good candidate for the use case. If pages need to update the <Menu/> component's props, then using context to manage state between the menu and pages might be a better solution in terms of architecture. Instead of rendering many <Menu/> components inside of each child, only one <Menu/> can be rendered higher up in the tree. This results in the component mounting once rather than many times with each child. Effectively, context manages the state of the menu, and provides methods to update state to any children under the provider. In this case, both child pages and the menu can update and respond to state updates.
import "./styles.css";
import React, { useContext, useMemo, useState } from "react";
// Create an instance of context so we are able to update the menu from lower in the tree
const menuContext = React.createContext({});
// Add state to the context provider. Wrap props earlier in the tree with this component.
const MenuContext = ({ children }) => {
const [pageKey, setPageKey] = useState("a");
const value = useMemo(() => ({ pageKey, setPageKey }), [pageKey]);
return <menuContext.Provider value={value}>{children}</menuContext.Provider>;
};
// The menu component which will:
// 1. Update the menuContext when the user selects a new pageKey
// 2. Respond to updates made to the pageKey by other components (in this case pages)
const Menu = () => {
const { pageKey, setPageKey } = useContext(menuContext);
React.useEffect(() => {
console.log("The menu remounted");
}, []);
return (
<div id="menu" className="has-scrollbar">
<button
onClick={() => setPageKey("a")}
style={{ color: pageKey === "a" ? "blue" : "red" }}
>
A
</button>
<button
onClick={() => setPageKey("b")}
style={{ color: pageKey === "b" ? "blue" : "red" }}
>
B
</button>
</div>
);
};
// In each page, we are able to update a value that is consumed by the menu using setPageKey
const PageA = () => {
const { setPageKey } = useContext(menuContext);
return (
<div>
<div>Content of page A</div>
<button onClick={() => setPageKey("b")}>Go to page B</button>
</div>
);
};
const PageB = () => {
const { setPageKey } = useContext(menuContext);
return (
<div>
<div>Content of page B</div>
<button onClick={() => setPageKey("a")}>Go to page A</button>
</div>
);
};
const PageComponent = () => {
const { pageKey } = useContext(menuContext);
switch (pageKey) {
case "b":
return <PageB key="2" />;
default:
return <PageA key="1" />;
}
};
const App = () => (
<MenuContext>
<Menu />
<PageComponent />
</MenuContext>
);
ReactDOM.render(<App />, document.querySelector("#app"))

how to pass pass details of item using routes

I've fetched a tracklist from API and when I click on the track name I have to be redirected to Details page where description of current track is displayed.
This is component where I fetch data and display in the list.
const TrackList = () => {
const url = `http://ws.audioscrobbler.com/2.0/?method=chart.gettoptracks&api_key=key=json`
const [trackList, setTrackList] = useState([])
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
const res = await fetch(url)
const data = await res.json()
setTrackList(data.tracks.track)
console.log(data.tracks.track)
}
return (
<div>
<Container>
<h1 className='mb-5 mt-5'>Top TrackList</h1>
{trackList.map(item => {
return (
<Row className='mt-1' style={{padding: '5px', border: '1px solid #000', display: 'flex', justifyContent: 'flex-start', alignItems: 'center'}}>
<Col lg={1} md={1} sm={1}>
<a href={item.artist.url}><img src={item.image[1]['#text']} /></a>
</Col>
<Col lg={11} md={11} sm={11}>
<Link to='/:mbid'><h6>{item.artist.name}</h6></Link>
<p>"{item.name}"</p>
</Col>
</Row>
)
})}
</Container>
</div>
)
}
Here I created Details page where main info has to be displayed :
const TrackListDetails = () => {
console.log('props', this.props.match.mbid)
return (
<Container>
</Container>
)
}
But Routes I used in App.js
Am I right ?
function App() {
return (
<div>
<Router>
<NavBar />
<Route path="/" component={TrackList}/>
<Route path="/details/:mbid" component={TrackListDetails}/>
</Router>
</div>
);
}
As stated in react router documentation you can pass state property to link element
<Link
to={{
pathname: "/courses",
state: { description: 'some description' }
}}
/>
You can use it in details page like this:
const { state } = useLocation();
const { description } = state;
But the problem is that you have to persist description when user reloads page. That's why I recommend fetching track details when details page is mounted.

Resources