React JS - call function however you get to a page/component - reactjs

I'm working on a page fetching data and then showing a list of results. I have a main App component with a navbar and 3 possible sub-pages. The first is the home, where you can also do a research as i mentioned. When you do that, i made it so that the beginning of the list scrolls to the top of the browser window (banner, navbar and such get out of the screen, on the top). Now, if you click one of the results, you get to a detailed page about that.
What i'm trying to achieve is to call the page scroll i mentioned also when you get back to the home, by using "previous page" button or even by clicking home in the navbar. The results list is still there, so i want to scroll it again on the top.
The function i use for the scroll is
const scrollPage = () => {
setTimeout(function () {
const element = document.getElementById("comment");
element.scrollIntoView({
behavior: "smooth",
block: "start",
});
}, 1000); }
and this works when i do the research, since i call it inside the search function. i tried calling the same function with useEffect() but it didn't work, also i tried like
useEffect(() => {
window.addEventListener('load', scrollPage);
return () => {
window.removeEventListener('load', scrollPage);
}}, []);
but it doesn't work. if i click home in the navbar it goes on top of the page and if i use the "previous page" button i get back at the same Y of the list and the beginning of the list doesn't get scrolled.

Related

How do I prevent unnecessary, repetitive side-effects from my React useEffect hook?

I am having trouble preventing unnecessary side-effects from a useEffect hook linked to a component that gets re-rendered/reused multiple times after being clicked. I want the side-effect to trigger an action once, but it is being triggered for every render.
I'm building a task viewer that displays nested task data. Please see screenshot #1 for reference.
For context, the main display shows the main tasks (see Event Data) and renders a clickable button if the task has sub-events. When this button is clicked, the selected main task is displayed at the top of the hierarchy view (see Event Hierarchy) and its sub-events are displayed below the hierarchy in a separate pane (see Event Details).
Like the main tasks, if these sub-events in 'Event Details' have their own sub-events, they are also rendered with a clickable button. When this sub-event button is clicked, this clicked sub-event is added to the bottom of the hierarchy, where the clicked main task is already displayed in bold. This selected sub-event's sub-events then replace the content in the 'Event Details' pane.
As the user clicks through the nested data, the clicked sub-event is added to the bottom of the hierarchy so that the user has an idea of where he is in the nested data and its sub-events displayed in 'Event Details'. All 'Event Hierarchy' and 'Event Details' data is cleared when the user selects a new main event or selects a new page.
The hierarchy events are held in an array managed via useState and every time another sub-event is clicked, it is added to this array. That's the idea, at least.
#1
My problem is this:
If I place my setHierarchy function inside a useEffect hook with the selectedTask as dependency, it renders the selectedTask in the hierarchy instantaneously, but the button component that triggers setHierarchy is re-rendered for every sub-event being displayed in 'Event Details' (as I want each event to be clickable) and in doing so, it adds that many copies of the event to my hierarchy array. This happens even though I am checking to see if the hierarchy array already contains the selected subevent before adding it. See result in screenshot #2.
I have tried various configurations of checking the array, but I cannot seem to stop it from adding these copies to and subsequently displaying them in the Hierarchy.
If I place the setHierarchy function inside my click handler, only one single event is added, but it executes before the selectedSubEvent has been updated. This means the hierarchy array is empty upon first render and stays one click 'behind' ie. a specific event is only displayed upon the following click event, after the click that selected it.
#2
This is all done inside my ExpandSubEvents button component (see code below) and also managed via a context provider.
I have tried moving the setHierarchy into a separate function, inside a useCallback, and triggering it from both the clickHandler and the useEffect that sets the selectedSubEvent. This did not resolve the issue.
I've also tried useRef to try and link it to the latest state. I'm not sure that's even doable/correct.
What am I doing wrong here? I am fairly new to coding, so any input on this would be much appreciated.
Sidenote: I suspect that my setup is perhaps beyond the intended scope of useContext. Is it? What can I do to make improvements? Is this perhaps in any way responsible for my issue?
Thank you for taking your time to read this far. I appreciate it!
Deon
ExpandSubEvents Component
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
import SubEventContext from '../../store/sub-event-context';
import classes from './ExpandSubEvents.module.css';
const ExpandSubEvents: React.FC<{
id: number;
subEvents: number;
}> = React.memo((props) => {
// Extract context
const subEventCtx = useContext(SubEventContext);
const {
subEvents,
subEventParentId,
selectedSubEvent,
hierarchy,
setSubEventParentId,
setFetchId,
setSelectedSubEvent,
setHierarchy,
} = subEventCtx;
// Get id of event for when it is clicked
const id = React.useMemo(() => props.id, [props.id]);
let eventIds: number[] = useMemo(() => [], []);
if (hierarchy) {
for (const event of hierarchy) {
eventIds.push(event.id);
}
}
// Set CSS classes to style button if it has sub-events
let subEventQuantity = props.subEvents;
let importedClasses = `${classes['sub-event-button']}`;
if (subEventQuantity === 0) {
importedClasses = `${classes['no-sub-events']}`;
}
// Push the event to the Hierarchy display
// NOTE Tried moving the setHierarchy to a separate function, but it did not make a difference
// const triggerHierarchy = useCallback(() => {
// if (!eventIds.includes(id))
// setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
// }, [eventIds, id, selectedSubEvent, setHierarchy]);
// Respond to subevent button click event
const clickHandler = useCallback(() => {
setSubEventParentId(id);
setFetchId(id);
// This setHierarchy works, but executes before the selectedSubEVent has been updated
// Furthermore, if a new subevent is selected, it checks if the NEW clicked one has been added
// BUT sends the OLD event still in selectedSubEvent to the hierarchy before IT has been updated
// meaning that the check does not stop the same event being added twice
if (!eventIds.includes(id))
setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
}, [
eventIds,
id,
selectedSubEvent,
setFetchId,
setHierarchy,
setSubEventParentId,
]);
// NOTE Tried useRef to get setHierarchy to use the latest selectedSubEvent
// const subEventRef = useRef<Event[]>([]);
// subEventRef.current = hierarchy;
// Trying to setHierarchy directly from its own useEffect
// useEffect(() => {
// if (!eventIds.includes(id))
// setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
// }, [eventIds, hierarchy, id, selectedSubEvent, setHierarchy]);
// Filter the event from the subEvent array and set it to selectedSubEvent
useEffect(() => {
setSelectedSubEvent(
subEvents.filter((subEvent) => subEvent.id === subEventParentId)
);
}, [setSelectedSubEvent, subEventParentId, subEvents]);
return (
<button onClick={clickHandler} className={importedClasses}>
{subEventQuantity}
</button>
);
});
export default ExpandSubEvents;

Get undefined in click function

I use react js for my application.
I have a function which is called when I click on the button.
const click = () => {
dispatch(postData)
console.log(selector.createUser.error.message;)
}
When I click on button submit i want to get the error message in console.log(selector.createUser.error.message;) , but i get undefined, and if i click second time i get the message.
Why it is happening and ho to solve? How to get the exact value in click function clicking first time?
const click = () => {
dispatch(postData);
console.log({selector.createUser.error.message});
}
Something like that should work. Depending on what selector is.
Note: this snippet doesn't actually work since nothing is defined but this shows how you would log a variable.

How to get background location of modal window from previous page, like Unsplash did?

The background page of modal window in Unsplash is really unique.
Emulation:
click on photo from feed
then click to another photo on modal page to watch effect
refresh page
click to go back on browser
notice the route of background location on modal window
So my question is how to create this background location properly?
Ciao, my personal point of view is that when you go back, Unsplash page re-triggers the modal window you open before. Unspalsh is written in React so, for example, code could be like that:
function Page() {
const [state, setState] = useState({...});
useEffect(() => {
// get data from cache
if (data) {
// (re)open dialog with photo
}
}, [state]); // state in deps so useEffect will be re-triggered each time you enter in Page
}
I don't want to be reductive but this could be a start point.

How override a material-ui variable or function

I'm using Material-UI component.
This one from git : https://github.com/mui-org/material-ui/blob/master/docs/src/pages/components/tabs/ScrollableTabsButtonAuto.js
My main problem is when i use the scroll button, that scroll too much ( i have lot of tabs, something like 20 tabs ). So when i click on scroll button, that scroll to the right a lot and i lose my active tab.
I have tried to check the code of Tabs.
I found the 2 function i have to override.
These functions are inside that js code:
https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Tabs/Tabs.js
var handleStartScrollClick = function handleStartScrollClick() {
moveTabsScroll(-tabsRef.current[clientSize]);
};
var handleEndScrollClick = function handleEndScrollClick() {
moveTabsScroll(tabsRef.current[clientSize]);
};
If i modify it on node_modules directy that work well, here an exemple :
var handleStartScrollClick = function handleStartScrollClick() {
moveTabsScroll(-tabsRef.current[clientSize]/10);
};
var handleEndScrollClick = function handleEndScrollClick() {
moveTabsScroll(tabsRef.current[clientSize]/10);
};
So now when i click on scroll button i have good result, it's scrolling step by step. But i can't directly overide into node_modules.
How can i do to do it correctly & properly please ?

restore scroll position via history push/replace state

i have a list where i can access detail views of my list items.
in my detail views i can navigate betweeen my list items.
i would like to have a back button on the detail view which brings me to my list view no matter how often i navigated in my detail views.
currently i tried this:
i pass a url: string to my navigation component.
thats the url to my list.
then i want to change my state when i click the back button:
const onClickBack = () => {
const stateData = {
path: window.location.href,
scrollTop: 300
}
window.history.replaceState(stateData, "", backURL)
const stateData2 = {
path: window.location.href,
scrollTop: 300
}
window.history.pushState(stateData2, "", backURL)
window.history.back()
}
i copied this code from history pushState and scroll position
no matter how often i navigated, i can go back to my list now. but how can i restore the whole state, e.g. the scroll position?
do i have to pass the whole state object?
It seems like you want to manually restore the scroll position. If this is the case, then you should tell the history API about your intention.
if ('scrollRestoration' in history) {
// Back off, browser, I got this...
history.scrollRestoration = 'manual';
}
This way, the scroll position of the previous URL will remain as well.
docs: https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration

Resources