I have a created popover menu, and I am trying to add the functionality of displaying this popover menu on the first visit to a page with the component. My idea was to use useEffect() and localStorage, however it seems that changes made to my usePopover() state do not seem to be saving.
const { showed, toggle, hide, show } = usePopover()
useEffect(() => {
if(!localStorage["alreadyVisited"]) {
localStorage["alreadyVisited"] = true;
show
}
}, [show])
Edit: The solution ended up being that I needed to change show to show()
You are not saving nor calling the storage , this is how should be done :
useEffect(() => {
if(!localStorage.getItem("alreadyVisited")) {
localStorage.setItem("alreadyVisited",true)
show
}
}, [show])
use this as a ref https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
Related
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.
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
Goal
Restore the scoll position of the window after the a div has toggled from position fixed to none.
Problem
Restoring the scroll position doesnt work although i get it saved correctly to the state.
Description
I have a page where a modal is opened via onClick. Therefore i created a "ToggleModalContext" to pass the props to the modal on the one hand and to the background div on the other hand. I want to modify the background div with setting the css property position to fixed to avoid that the background is scrolled instead of the content of the modal.
When the modal is closed, the position: fixed is removed and i want to restore the scroll position of the window.
This last step doesnt work. Maybe someone else has an idea?
ToggleModalContext (Thats the context, where the scroll restore function is called)
import React from "react";
export const ToggleModalContext = React.createContext();
export const ModalProvider = props => {
const [toggle, setToggle] = React.useState(false);
const [scrollPosition, setScrollPosition] = React.useState();
function handleToggle() {
if (toggle === false) {
setScrollPosition(window.pageYOffset); // When the Modal gets opened, the scrollposition is saved correctly
}
if (toggle === true) {
window.scrollTo(0, scrollPosition); // Restoring doesnt work.
}
setToggle(!toggle);
}
return (
<ToggleModalContext.Provider value={[toggle, handleToggle]}>
{props.children}
</ToggleModalContext.Provider>
);
};
Maybe somebody has a idea?
Maybe i have to use useEffect? But how?
Thanks for your time in advance :)
From the description you have provided, you are using a fixed position on the background div to remove scrolling on the window when you open your modal
On the other hand, you are calling
if (toggle === true) { window.scrollTo(0, scrollPosition);}
before your modal has closed. At this time, the background div is in a fixed position and there is no where to scroll to.
You need to ensure that your modal has safely closed and your background div is back to its normal position before calling this function. To see the behavior, you can use a setTimeout function and call this function there with a set time e.g.
setTimeout(() => window.scrollTo(0, scrollPosition), 2000);
I'm currently migrating to antd, and have a modal appear on a certain route (ie /userid/info). I'm able to achieve this if I use the antd Modal react component, but I'd like to be able to use the modal methods provided such as Modal.confirm,Modal.info and Modal.error as they offer nicer ui straight out of the box.
I'm running to multiple issues such as having the modal rendered multiple times (both initially and after pressing delete in the delete user case), and unable to make it change due to state (ie display loading bar until data arrives). This is what i've tried but it constantly renders new modals, ive tried something else but that never changed out of displaying <Loader /> even though isFetching was false. I'm not sure what else to try.
const UserInfoFC: React.FC<Props> = (props) => {
const user = props.user.id;
const [isFetching, setIsFetching] = React.useState<boolean>(true);
const [userInfo, setUserInfo] = React.useState<string>('');
const modal = Modal.info({
content: <Loader />,
title: 'User Info',
});
const displayModal = () => {
const renderInfo = (
<React.Fragment>
<p>display user.info</p>
</React.Fragment>
);
const fetchInfo = async () => {
try {
user = // some api calls
setUserInfo(user.info);
modal.update({ content: renderInfo })
} catch (error) {
// todo
}
setIsFetching(false);
};
fetchInfo();
};
displayModal();
return(<div />);
};
reference: https://ant.design/components/modal/#components-modal-demo-confirm
edit: here is a replication of one of the issues I face: https://codesandbox.io/embed/antd-reproduction-template-1jsy8
As mentioned in my comment, you can use a useEffect hook with an empty dependency array to run a function once when the component mounts. You can initiate an async call, wait for it to resolve and store the data in your state, and launch a modal with a second hook once the data arrives.
I made a sandbox here
Instead of going to /:id/info and routing to a component which would have returned an empty div but displayed a modal, I created a displayInfo component that displays a button and that controls the modal. I got rid of attempting to use routes for this.
What I have now is similar to the docs
I am currently using react-navigation to do stack- and tab- navigation.
Is it possible to re-render a component every time the user navigates to specific screens? I want to make sure to rerun the componentDidMount() every time a specific screen is reached, so I get the latest data from the server by calling the appropriate action creator.
What strategies should I be looking at? I am pretty sure this is a common design pattern but I failed to see documented examples.
If you are using React Navigation 5.X, just do the following:
import { useIsFocused } from '#react-navigation/native'
export default function App(){
const isFocused = useIsFocused()
useEffect(() => {
if(isFocused){
//Update the state you want to be updated
}
}, [isFocused])
}
The useIsFocused hook checks whether a screen is currently focused or not. It returns a boolean value that is true when the screen is focused and false when it is not.
React Navigation lifecycle events quoted from react-navigation
React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: willFocus, willBlur, didFocus and didBlur. Read more about them in the API reference.
Let's check this out,
With navigation listeners you can add an eventlistener to you page and call a function each time your page will be focused.
const didBlurSubscription = this.props.navigation.addListener(
'willBlur',
payload => {
console.debug('didBlur', payload);
}
);
// Remove the listener when you are done
didBlurSubscription.remove();
Replace the payload function and change it with your "refresh" function.
Hope this will help.
You can also use also useFocusEffect hook, then it will re render every time you navigate to the screen where you use that hook.
useFocusEffect(()=> {
your code
})
At the request of Dimitri in his comment, I will show you how you can force a re-rendering of the component, because the post leaves us with this ambiguity.
If you are looking for how to force a re-rendering on your component, just update some state (any of them), this will force a re-rendering on the component. I advise you to create a controller state, that is, when you want to force the rendering, just update that state with a random value different from the previous one.
Add a useEffect hook with the match params that you want to react to. Make sure to use the parameters that control your component so it rerenders. Example:
export default function Project(props) {
const [id, setId] = useState(props?.match?.params?.id);
const [project, setProject] = useState(props?.match?.params?.project);
useEffect(() => {
if (props.match) {
setId(props.match?.params.id);
setProject(props.match?.params.project);
}
}, [props.match?.params]);
......
To trigger a render when navigating to a screen.
import { useCallback } from "react";
import { useFocusEffect } from "#react-navigation/native";
// Quick little re-render hook
function useForceRender() {
const [value, setValue] = useState(0);
return [() => setValue(value + 1)];
}
export default function Screen3({ navigation }) {
const [forceRender] = useForceRender();
// Trigger re-render hook when screen is focused
// ref: https://reactnavigation.org/docs/use-focus-effect
useFocusEffect(useCallback(() => {
console.log("NAVIGATED TO SCREEN3")
forceRender();
}, []));
}
Note:
"#react-navigation/native": "6.0.13",
"#react-navigation/native-stack": "6.9.0",