React settimeout in useeEffect is called every time I navigate back - reactjs

I'm trying to change the style of an element within setTimeout and useEffect.
Here is how the code looks like
In home.js:
useEffect(()=>{
setTimeout(()=>{
// change styles for 'frontenddevelopment'
changeStyles1();
},1000)
// changeStyles for 'i love frontend'
changeStyles2()
},[])
I found that after the home page is rendered if I navigate to other pages and come back to home page, changesStyles1() will be called again and animation will run again. What should I do to avoid calling setTimeout() again when I navigate back to home page from other pages.
You will notices that changeStyle2 won't be animated again if I navigate from home page to other page and then back to home page. And this is the effect I want.
My website is hosted on netlify
My code on codepen

I would manage it from the parent component.
Lets assume the following component tree:
App
Home
Page1
In App:
const [isFirstMount, setFirstMount] = useState(true);
//...
return(
<Home ...props changeStyle={isFirstMount} onStyleChanged={() =>
setFirstMount(false)}/>
//...
)
In Home:
if (props.changeStyle) {
let pnt = setTimeout(()=>{
// change styles for 'frontenddevelopment'
changeStyles1();
onStyleChanged()
clearTimeout(pnt);
}, 1000)
}

You need to clear the timer, otherwise, you may experience adverse side effects in your code.
useEffect(()=>{
const timer = setTimeout(()=>{
// change styles for 'frontenddevelopment'
changeStyles1();
}, 1000)
// changeStyles for 'i love frontend'
changeStyles2()
//clear the timer
return () => clearTimeout(timer);
},[]);
see this link for more info.

Related

How to Redirect to New Page and Fire up the Action?

Using NextJS and Redux.
Let me briefly explain as it seems complicated without understanding the website mechanic.
My Website Buttons:
Home (Goes to homepage)
Search (Opens search menu in homepage)
Sign In (Goes to sign in page)
Imagine having 3 buttons in the navigation bar. First button goes to '/' page. Second button's function is to open up a sliding menu that is only available in page '/'. Third button takes you to '/sign-in' page. Remember the second button. So if the second button is clicked when the website is on '/' page, there is no problem with the sliding menu opening and closing. But, if lets say I am in '/sign-in' page and clicked on the sliding menu opening button, I want my website to first go to '/' page, then open up the sliding menu.
Snippet goes to the '/' page but fails to execute the next line of code.
const searchClickHandler = useCallback(() => {
if (window.location.pathname !== '/') {
router.push('/');
}
dispatch(toggleFilterMenu());
}, [dispatch, router]);
I tried using Thunk principle inside Redux but as you may know useRouter hook cannot be used inside a Redux file. I tried async await keywords but dispatch method gives warning saying that I cannot use await for dispatch method.
Any help would be appreciated.
You could pass some "state" in the PUSH to the "/" route and check this in the receiving component. In other words, effect a navigation to the "/" route first, and then in that component check if the search menu should be opened.
Example:
const searchClickHandler = useCallback(() => {
router.push(
{
pathname: "/",
query: { openMenu: true }
},
"/"
);
}, [dispatch, router]);
const router = useRouter();
useEffect(() => {
if (router.query.openMenu) {
dispatch(toggleFilterMenu());
}
}, [router]);
If I am understanding correctly. Then, You can use the useEffect. Hook to trigger the function that opens the sliding menu after the component has finished rendering.
import { useEffect } from 'react';
const searchClickHandler = useCallback(() => {
dispatch(toggleFilterMenu());
}, [dispatch]);
useEffect(() => {
if (window.location.path !== '/') {
router.push('/');
}
searchClickHandler();
}, [searchClickHandler, router]);
When the button is clicked, hook will be triggered and will check the and the current path, and then it's open the menu.
OK actually, I found out that both answers and my method actually works. Problem was with another action called resetSlidingMenuStates intercepts with what I want to accomplish in every new page reload... I spent 2 hours on this but now while tinkering, found out it was because of another action I put.
We can lock the this thread. Thanks.

Scroll div to bottom when rendered React

I have a div of Messages in my chat application written in React. I want the div to be scrolled to the bottom when the user loads the page for the first time in order to see the latest messages. How can I implement this?
you can use useLayoutEffect hook to see when your div is rendered and use a refrence to your div element to scrollit to end
const Messages = () => {
const divRef = useRef();
...
useLayoutEffect(() => {
if (divRef.current)
divRef.current.scrollTop = divRef.current.scrollHeight;
}, [divRef]);
...
return (
<div ref={divRef}>
...
</div>
);
};
You just need to write useEffect hook either at the start when the component renders or when all the messages are fetched. Something like this
React.useEffect(() => {
window.scrollTo(0,document.body.scrollHeight);
}, []);
or
React.useEffect(() => {
window.scrollTo(0,document.body.scrollHeight);
}, [messages]);
Depends on how you're components are structured or how the views are managed. But this is the outline how you can trigger scrollTo when a user goes to your message screen.

Pagetransition before next page in Nextjs

Is there any way to archive a page transition that fits the following requirements:
I want to show a page transition when a page changes
The page transition should finish before the next page loads/shows
The page transition should run X seconds
The page transition is a component placed in _app.js
Currently, I do this in a gruesome way.
In NuxtJS, it is possible to archive it via the Javascript Hooks. https://nuxtjs.org/docs/2.x/components-glossary/pages-transition
Thank you for the help :)
You can hook into Next.js Router events, but you can not set how much time the transition should take.
E.g. if you want the transition to be 3 seconds:
if the Next.js transition takes 1 second, you can wait 2 seconds, but
if the Next.js transition takes 4 seconds, you can't do anything about it.
And you can not know for sure how long the Next.js transition will take.
custom "routing"
Anyway, if you rely on the Next.js transition never taking more time than your animation, you would have to "store" the old page view somehow, and show it instead of the new page as long as the animation runs.
From Next.js perspective the new page should be shown. I think this would not work with Next.js routing, you would have to do the "routing" (display of content depending on the url) yourself, I guess. E.g. design your App to always have something like two pages at once, one is shown, one is hidden and might be loading. Then you can switch between these two pages whenever you want.
You might want to have a look at the experimental feature React "Suspense" (I don't know, I haven't used it).
wait before routing
If you want to wait a while before the transition starts, you might do something like this:
const startAnimation = () => {
return new Promise( ( resolve, reject ) => {
console.log('...animate...');
setTimeout( resolve, 3000 ); // <-- replace this line with your animation, and call resolve() when finished
} );
};
const routerWrapper = {
push: ( url ) => {
startAnimation().then( () => {
console.log('Next.js routing starts...');
Router.push( url );
})
}
};
// ...
routerWrapper.push( newUrl ); // <-- this instead of Router.push( newUrl )
stop animation when routing completed
If you want to start an animation when Route.push() is called, and stop it when the Next.js transition is completed, you can use Next.js routerevents, e.g.:
export default function App( props: AppProps ) {
const { Component, pageProps } = props;
const router = useRouter()
useEffect(() => {
const routeChangeStart = (url, { shallow }) => {
console.log('start animation');
};
const routeChangeComplete = (url, { shallow }) => {
console.log('stop animation');
};
router.events.on('routeChangeStart', routeChangeStart);
router.events.on('routeChangeComplete', routeChangeComplete);
return () => {
router.events.off('routeChangeStart', routeChangeStart);
router.events.off('routeChangeComplete', routeChangeComplete);
}
}, [])
// --
return (
<Component { ...pageProps } />
);
}
I have not personally done this yet, but it looks like you can do something like this with Framer Motion.
Destructuring props from the router in your main App component
function App({ Component, pageProps, router }) {
Then you can use the AnimatePresence component from Framer Motion to animate the route load and exit. You said you were familiar with the library in the comments so I won't talk about that component but here is what you add to the motion.div within the AnimatePresence component
<motion.div key={router.route}>
Below I will link a good article on how to do it and a good repo to use as an example. I did not write either of these but they are quite useful and descriptive.
https://www.freecodecamp.org/news/how-to-add-interactive-animations-and-page-transitions-to-a-next-js-web-app-with-framer-motion/#step-3-adding-page-transitions-with-framer-motion-to-a-next-js-app
https://github.com/colbyfayock/my-rick-and-morty-wiki/commit/3c1455370f750ff86b75a3b3edc446ebe553bf5b

React useEffect strange behaviour with custom layout component

I'm trying to use scroll position for my animations in my web portfolio. Since this portfolio use nextJS I can't rely on the window object, plus I'm using navigation wide slider so I'm not actually scrolling in the window but in a layout component called Page.
import React, { useEffect } from 'react';
import './page.css';
const Page = ({ children }) => {
useEffect(() => {
const scrollX = document.getElementsByClassName('page')
const scrollElement = scrollX[0];
console.log(scrollX.length)
console.log(scrollX)
scrollElement.addEventListener("scroll", function () {
console.log(scrollX[0].scrollTop)
});
return () => {
scrollElement.removeEventListener("scroll", () => { console.log('listener removed') })
}
}, [])
return <div className="page">{children}</div>;
};
export default Page;
Here is a production build : https://next-portfolio-kwn0390ih.vercel.app/
At loading, there is only one Page component in DOM.
The behaviour is as follow :
first listener is added at first Page mount, when navigating, listener is also added along with a new Page component in DOM.
as long as you navigate between the two pages, no new listener/page is added
if navigating to a third page, listener is then removed when the old Page is dismounted and a new listener for the third page is added when third page is mounted (etc...)
Problem is : when you navigate from first to second, everything looks fine, but if you go back to the first page you'll notice the console is logging the scrollX value of the second listener instead of the first. Each time you go on the second page it seems to add another listener to the same scrollElement even though it's not the same Page component.
How can I do this ? I'm guessing the two component are trying to access the same scrollElement somewhat :/
Thanks for your time.
Cool site. We don't have complete info, but I suspect there's an issue with trying to use document.getElementsByClassName('page')[0]. When you go to page 2, the log for scrollX gives an HTMLCollection with 2 elements. So there's an issue with which one is being targeted. I would consider using a refs instead. Like this:
import React, { useEffect, useRef } from 'react';
import './page.css';
const Page = ({ children }) => {
const pageRef = useRef(null)
const scrollListener = () => {
console.log(pageRef.current.scrollTop)
}
useEffect(() => {
pageRef.addEventListener("scroll", scrollListener );
return () => {
pageRef.removeEventListener("scroll", scrollListener )
}
}, [])
return <div ref={pageRef}>{children}</div>;
};
export default Page;
This is a lot cleaner and I think will reduce confusion between components about what dom element is being referenced for each scroll listener. As far as the third page goes, your scrollX is still logging the same HTMLElement collection, with 2 elements. According to your pattern, there should be 3. (Though there should really only be 1!) So something is not rendering properly on page 3.
If we see more code, it might uncover the error as being something else. If refs dont solve it, can you post how Page is implemented in the larger scope of things?
also, remove "junior" from the "junior developer" title - you won't regret it

useEffect not called in React Native when back to screen

How are you.
This is scenario of this issue.
Let's say there are 2 screens to make it simple.
enter A screen. useEffect of A screen called.
navigate to B screen from A screen
navigate back to A screen from B.
at this time, useEffect is not called.
function CompanyComponent(props) {
const [roleID, setRoleID] = useState(props.user.SELECTED_ROLE.id)
useEffect(()=>{
// this called only once when A screen(this component) loaded,
// but when comeback to this screen, it doesn't called
setRoleID(props.user.SELECTED_ROLE.id)
}, [props.user])
}
So the updated state of Screen A remain same when comeback to A screen again (Not loading from props)
I am not changing props.user in screen B.
But I think const [roleID, setRoleID] = useState(props.user.SELECTED_ROLE.id) this line should be called at least.
I am using redux-persist. I think this is not a problem.
For navigation, I use this
// to go first screen A, screen B
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// when come back to screen A from B
function goBack() {
_navigator.dispatch(
NavigationActions.back()
);
}
Is there any callback I can use when the screen appears?
What is wrong with my code?
Thanks
Below solution worked for me:
import React, { useEffect } from "react";
import { useIsFocused } from "#react-navigation/native";
const ExampleScreen = (props) => {
const isFocused = useIsFocused();
useEffect(() => {
console.log("called");
// Call only when screen open or when back on screen
if(isFocused){
getInitialData();
}
}, [props, isFocused]);
const getInitialData = async () => {}
return (
......
......
)
}
I've used react navigation 5+
#react-navigation/native": "5.6.1"
When you navigate from A to B, component A is not destroyed (it stays in the navigation stack). Therefore, when you navigate back the code does not run again.
Perhaps a better way to acheive what you want to to use the navigation lifecycle events (I am assuming you are using react-navigation) I.e. subscribe to the didFocus event and run whatever code you want whenever the component is focussed E.g
const unsubscribe = props.navigation.addListener('didFocus', () => {
console.log('focussed');
});
Don't forget to unsubscribe when appropriate e.g.
// sometime later perhaps when the component is unmounted call the function returned from addListener. In this case it was called unsubscribe
unsubscribe();
The current version of React Navigation provides the useFocusEffect hook. See here.
React Navigation 5 provide a useFocusEffect hook, is analogous to useEffect, the only difference is that it only runs if the screen is currently focused. Check the documentation https://reactnavigation.org/docs/use-focus-effect
useFocusEffect(
useCallback(() => {
const unsubscribe = setRoleID(props.user.SELECTED_ROLE.id)
return () => unsubscribe()
}, [props.user])
)
The above mentioned solution would work definitely but in any case you need to know why it happens here is the reason,
In react native all the screens are stacked meaning they follow the LAST-IN-FIRST-OUT order, so when you are on a SCREEN A and go.Back(), the component(Screen A) would get un-mounted because it was the last screen that was added in the stack but when we naviagte to SCREEN B, it won't get un-mounted and the next SCREEN B would be added in the stack.
So now, when you go.Back() to SCREEN A, the useEffect will not run because it never got un-mounted.
React-native keeps the navigation this way to make it look more responsive and real time.
if you want to un-mount the Screen everytime you navigate to some other screen you might want to try navigation.replace than navigation.navigate
Hope this helps you.
import { useIsFocused } from "#react-navigation/native";
const focus = useIsFocused(); // useIsFocused as shown
useEffect(() => { // whenever you are in the current screen, it will be true vice versa
if(focus == true){ // if condition required here because it will call the function even when you are not focused in the screen as well, because we passed it as a dependencies to useEffect hook
handleGetProfile();
}
}, [focus]);
more
Solution with useEffect
import { useNavigation } from '#react-navigation/native';
const Component = (props) => {
const navigation = useNavigation()
const isFocused = useMemo(() => navigation.isFocused(), [])
useEffect(() => {
if (isFocused) {
// place your logic
}
}, [isFocused])
}

Resources