Scroll to top when changing pages - javascript

I have a pagination component (React). I need the scroll to be moved to the top when changing pages. Can someone help me?
const MAX_BTN = 9;
const MAX_BTN_LEFT = (MAX_BTN - 1) / 2;
const Pagination = ({items, limit = 20, page, setPage}) => {
const pages = items !== null && Math.ceil(items.total_results / limit);
const startBtn = Math.max(page - MAX_BTN_LEFT, 1);
return (
<ul className="pagination">
{Array(Math.min(MAX_BTN, pages)).fill()
.map((_, i) => i + startBtn)
.map(value => (
<li key={value}>
<button onClick={() => setPage(value)}>{value}</button>
</li>
))
}
</ul>
)
}
export default Pagination;

You can return to the top with a function:
const handlePageChange = value => {
window.scrollTo(0, 0);
setPage(value);
}
And calling it in your button:
...
<li key={value}>
<button onClick={() => handlePageChange(value)}>{value}</button>
</li>

call the browser window object with below method scrollTo when you click on the Link
window.scrollTo(0, 0);

An elegant way to handle this is by creating a single ref for the top-most part of your app. Then pass the ref into a hook which handles scrolling to the top of the page when the pathname changes.
Here's a simple hook I use (type definitions can be cleaned up):
useScrollToTop.ts
import { useEffect } from 'react';
import { useLocation } from 'react-router';
const useScrollToTop = (ref: any) => {
const location = useLocation();
// ensure pages scroll to top
useEffect(() => {
if (ref?.current) {
ref.current.scrollIntoView();
}
}, [ref?.current, location.pathname]);
return null;
};
export default useScrollToTop;
Now at the top of your app's very first element add the reference.
App.js: (or whatever component)
import useScrollToTop from './useScrollToTop';
Then create a new empty ref:
const ref = useRef<HTMLDivElement | null>(null);
Pass the ref into the hook:
useScrollToTop(ref)
Define the ref on the top-most part of your app (example inner components):
return (
<div ref={ref}>
<Header />
<Navigation />
<Content />
<Footer ?>
</div>
);
Now anytime the path changes, your end-user will scroll to the top of the page.

Related

Creating tab component in React and handling click events for child components

New to React and trying to build a tabular component. I know I'm reinventing the wheel but I'm trying to take this as a learning experience.
Here is how I intend to use the component:
<Tabs>
<Tabs.MenuItems>
<Tabs.MenuItem>Tab item 1</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 2</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 3</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 4</Tabs.MenuItem>
</Tabs.MenuItems>
<Tabs.Panes>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
</Tabs.Panes>
</Tabs>
My current implementation works in displaying the items properly. But the one challenge I am facing is being able to handle the onClick event for the Tabs.MenuItem. I understand that I should not be handling the onClick in the Tabs.MenuItem child component, and rather should be handled in the upmost parent Tabs component.
I tried using forwardedRef but that posed some limitations in accessing the props.children. Even if I managed to get it working syntactically, I am not even sure how the Tabs component is suppose to access that ref.
The idea here is that depending on what Tabs.MenuItem is in an active state, it will correspond to the same child Tabs.Pane component index to render that pane.
import React, { forwardRef, useState } from "react";
const Tabs = (props, { activePane }) => {
return (
props.children
);
}
const MenuItems = (props) => {
React.Children.forEach(props.children, child => {
console.log(child);
})
return (
<div className="ui secondary menu" style={props.style}>
{props.children}
</div>
)
}
const MenuItem = (props) => {
const [isActive, setActive] = useState(false);
return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a className={isActive ? "item active" : "item"} onClick={() => setActive(!isActive)}>{props.children}</a>
)
}
// const MenuItem = forwardRef((props, ref) => (
// // eslint-disable-next-line jsx-a11y/anchor-is-valid
// <a ref={ref} className="item">{props.children}</a> // error accessing props.children
// ))
const Panes = (props) => {
return (
props.children
)
}
const Pane = (props) => {
return (
props.children
)
}
Tabs.MenuItems = MenuItems;
Tabs.MenuItem = MenuItem;
Tabs.Panes = Panes;
Tabs.Pane = Pane;
export default Tabs;
I am not looking for someone to complete the entire tabular functionality, just an example of how I can forward the children references to the topmost parent so that I can handle click events properly.

How can I add props to a compound child component without using top level api methods?

I am attempting to create a Panel component that has the option to be wrapped in a PanelGroup component. The PanelGroup components main function is the query the children Panel components and determine if certain conditions so it can add a z-index prop to each of them according to the conditions.
Is it a left, right, top or bottom positioned Panel
Is it a nested Panel
Each condition will require a z-index that may be higher than the others and also placed in front of the highest z-index found on the page. I would like to be able to iterate though the children inside a useEffect and after logic is ran to determine the hierarchy, add the z-index to each child as a prop. I have attempted to use React.Children mapping through and using cloneElement to add the z-index, however the issue is this method must be called inside the render/return and when a second panel is opened it causes any other panel that is open to close and open again. Is there a way to update the props without using the top level React API?
PanelGroup API
<PanelGroup>
<Panel
id={'1'}
open={open1}
onClosed={handleClose1}
controller={true}
renderPortal={true}
position={PanelPosition.RIGHT}
>
<div>
<h2>Panel Slide Left</h2>
<button onClick={handleClose1}>Close</button>
</div>
</Panel>
<Panel
id={'2'}
open={open2}
onClosed={handleClose2}
controller={true}
renderPortal={true}
position={PanelPosition.LEFT}
>
<div>
<h2>Panel Slide Right</h2>
<button onClick={handleClose2}>Close</button>
</div>
</Panel>
</PanelGroup>
This is what I tried which does not work
const PanelGroup = ({
children
}: PanelGroupProp): JSX.Element => {
const [highestZIndex, setHighestZIndex] = React.useState(null);
/**
* Handles finding the highest index on the page after render. This value
* will be used as the benchmark to set the z-index of the panel
* components managed by this component.
*/
React.useEffect(() => {
const highestIndex = getHighestZIndex();
setHighestZIndex(highestIndex);
}, []);
const renderGroupPanelChildren = (children): React.ReactElement => {
let incrementor = highestZIndex + 1;
return (
<React.Fragment>
{React.Children.map(children || null, (child) => {
const newProps = {...child.props, ZIndex: incrementor };
incrementor++;
return <child.type {...newProps } key={getGuid()} />;
})}
</React.Fragment>
);
};
return (
<>
<PanelOverlay visibility={false} />
{renderGroupPanelChildren(children)}
</>
);
};
I would like to find a way to do something like:
const PanelGroupComponent = ({
children
}: PanelGroupProp): JSX.Element => {
const [highestZIndex, setHighestZIndex] = React.useState(null);
const [elements, setElements] = React.useState(null);
/**
* Handles finding the highest index on the page after render. This value
* will be used as the benchmark to set the z-index of the panel
* components managed by this component.
*/
React.useEffect(() => {
const highestIndex = getHighestZIndex();
setHighestZIndex(highestIndex);
}, []);
React.useEffect(() => {
// logic to set z-index's ....
const elements ....
setElements(elements);
}, [highestIndex]);
return (
<>
<PanelOverlay visibility={false} />
{elements}
</>
);
};

Warning: Cannot update a component (`App`) while rendering a different component (`History`)

I am actually building a tic tac toe. But this error is not actually letting me update history. As I am following a tutorial on skillshare.com and I did the same as he does. But still getting error. I am a beginner in React. And I use a nano-react npm project for creating this project.
This is App.js:
import React , {useState} from "react";
import Board from "./components/Board"
import History from "./components/History"
import {calculateWinner} from './support'
import StatusMessage from './components/StatusMessage'
import './styles/root.scss'
const NEW_GAME = [
{
board: Array(9).fill(null),
isXNext : true
}
]
const App = () => {
const [history, setHistory] = useState(NEW_GAME);
const [currentMove, setCurrentMove] = useState(0);
const current = history[currentMove];
const {winner , winningSquare} = calculateWinner(current.board);
const clickHandleFunction = (position) => {
if (current.board[position] || winner) {
return;
}
setHistory((prev) => {
const last = prev[prev.length-1];
const newBoard = last.board.map((square, pos) => {
if (pos === position) {
return last.isXNext ? 'X' : '0';
}
return square;
});
return prev.concat({board: newBoard, isXNext : !last.isXNext})
});
setCurrentMove(prev => prev +1);
};
const moveTo = (move) => {
setCurrentMove(move);
}
const onNewGame = () => {
setHistory(NEW_GAME);
setCurrentMove(0);
}
return(
<div className="app">
<h1>TIC TAC TOE</h1>
<StatusMessage winner ={winner} current ={current}/>
<Board board = {current.board} clickHandleFunction = {clickHandleFunction} winningSquare = {winningSquare}/>
<button type="button" onClick = {onNewGame}>Start New Game</button>
<History history={history} moveTo = {moveTo} currentMove = {currentMove} />
</div>
)
}
export default App;
And It is my History.js:
import React from 'react.'
function History({history, moveTo, currentMove}) {
return (
<ul>
{
history.map((_, move) => {
return( <li key={move}> <button style={{
fontWeight: move === currentMove ? 'bold' : 'normal'
}} type="button" onClick = {moveTo(move)} >
{move === 0 ? 'Go to game start!': `Gove to move #${move}`} </button> </li> );
})
}
</ul>
)
}
export default History
The problem is in History.js:
onClick={moveTo(move)}
You need to provide a function in the onClick prop. Instead, you are calling the moveTo function and passing its return value as the onClick prop.
Due to this, whenever React renders the History component, it also inadvertently calls the moveTo function which triggers an update in the App component. This is what the error says - can't update another component while rendering a component.
To fix this, change moveTo(move) to () => moveTo(move). Now you pass a function into onClick that will call the moveTo function when the user clicks. Working sandbox: https://codesandbox.io/s/practical-frog-tcyxm?file=/src/components/History.js
In React Navigation move the setParams and setOptions inside method componentDidMount() in class components or in useEffects() hook in functional components.

actions are dispatching every time i render a child component

I am new to react and kind of experimenting with the hooks and i am stuck in a problem where i am using useEffect to dispatch an action to redux store. so everything works fine but the problem i am facing is every time i render a child component in my main component it dispatch the action. Like there are cards that are basically child components and whenever i click on of them to show more details it dispatch the actions that are in parent components same if i close the component so my question how can i prevent that from happening and only render the items once. Let me know if you need any other code..
Parent Component
import React, { useEffect } from "react";
//ANIMATION AND STYLED
import styled from "styled-components";
import { motion, AnimatePresence, AnimateSharedLayout } from "framer-motion";
//REDUX and ROUTER
import {
AllPopularGame,
NextPage,
PrevPage,
} from "../Actions/popularGameActions";
import { useSelector, useDispatch } from "react-redux";
import { Link, useLocation, useHistory } from "react-router-dom";
//COMPONENTS
import Game from "./games";
import GameDetail from "./gameDetail";
const PopularGames = () => {
//GETTNG PATH
const Location = useLocation();
const History = useHistory();
const pathId = Location.pathname.split("/")[4];
//Redux store
const { allPopularGame, gameCount, currentPage, gameLoading } = useSelector(
(state) => state.popular
);
//No of pages
const totalPage = Math.ceil(gameCount / 36);
//SCROLL TO TOP
useEffect(() => {
window.scrollTo(0, 0);
}, [currentPage]);
//Handlers
const PrevHandler = () => {
if (currentPage <= 1) {
return;
} else {
dispatch(PrevPage());
History.push(`/popular/games?page=${currentPage - 1}`);
}
};
const NextHandler = () => {
if (currentPage >= totalPage) {
console.log("Hello");
return;
} else {
dispatch(NextPage());
History.push(`/popular/games?page=${currentPage + 1}`);
}
};
//Fetch all popular games
const dispatch = useDispatch();
useEffect(() => {
async function fetchGames(page) {
const games = dispatch(AllPopularGame(page));
return games;
}
fetchGames(currentPage);
}, [dispatch, currentPage]);
// {`${currentPage} /popular/games/${popularGames.id}`}
return (
<Popular>
<h2>Popular Games </h2>
<AnimateSharedLayout type="crossfade">
<AnimatePresence>
{pathId && <GameDetail pathId={pathId} curPage={currentPage} />} //child component
</AnimatePresence>
{gameLoading ? (
<h2>Loading</h2>
) : (
<Games>
{allPopularGame.map((popularGames) => (
<Link
to={`/popular/games/${currentPage}/${popularGames.id}`}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</Link>
))}
</Games>
)}
</AnimateSharedLayout>
<Page>
<Button onClick={PrevHandler}>
<span>Prev</span>
</Button>
<p>{currentPage}</p>
<Button onClick={NextHandler}>
<span>Next</span>
</Button>
</Page>
</Popular>
);
};
Github repo
Current code
Thanks for sharing the repository! It's quite clear now. You're using a link and re-routing the page - so although the components are the same and there isn't a re-paint on the UI, the entire page still mounts again. That's why your useEffect gets triggered everytime you click on a card (and even when you close one!).
Your UI state is managed by the path in the URL - this is really bad practice - especially considering that you aren't making any API calls to fetch data based on the URL.
Here's what I would suggest -
Replace the pathID variable that you are currently reading from the URL with a useState hook:
const [activeGameID, setActiveGameID]=useState(null);
Replace the Link component that you use to wrap the Game with a regular div and pass it an onClick handler to setActiveGameID:
<Games>
{popular.map((popularGames) => (
<div
onClick={() => setActiveGameID(popularGames.id)}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</div>
))}
</Games>```
I think the problem you may be facing is that your animations are dependent on URL changes - in that case I would urge you to use some other way to animate your cards.

ReactJs Functional Component Get When Scroll Reached End

I have a functional component in ReactJs and want to add pagination to my page so that when the user reaches end of the screen, I'd get the rest of the data from Api.
my code looks like this:
<div
className="MyOrdersHistory-list-container">
{ordersList.map((order, index) => (
<div key={index} className="MyOrdersHistory-listItem-container">
........
</div>
))}
</div>
How can I figure out when use scrolls to the end of the page to fire the Api request. (If possible please give a functional component example not a class one)
Thanks
import React, { useRef, useEffect } from 'react';
const <YourComponent> = () => {
const list = useRef();
const onScroll = () => {
if (list.current) {
const { scrollTop, scrollHeight, clientHeight } = list.current;
if (scrollTop + clientHeight === scrollHeight) {
// DO SOMETHING WHAT YOU WANT
}
}
};
...
<div
onScroll={() => onScroll()} ref={list}
className="MyOrdersHistory-list-container">
{ordersList.map((order, index) => (
<div key={index} className="MyOrdersHistory-listItem-container">
........
</div>
))}
</div>

Categories

Resources