React Navbar pathname doesn't render properly on page change? - reactjs

So I'm trying to update my navbar background based on the page I am on, but when I console.log the window.location.pathname, it doesn't show the pathname properly.
Here is my function to change the navbar state based on the pathname
const [navbar, setNavbar] = useState(false)
const updateNavbar = () => {
if (window.location.pathname !== "/") {
setNavbar(true)
} else {
setNavbar(false)
}
console.log(window.location.pathname)
}
Here's my styled component
const Nav = styled.nav`
background: ${({ navbar }) => (navbar ? "red" : "blue")};
Then I pass this into my JSX
<Nav navbar={navbar}>
{menuData.map((item, index) => (
<NavLink to={item.link} key={index} onClick={updateNavbar}>
{item.title}
</NavLink>
))}
The issue is that if I click on my about menu item, in the console it shows
/
then if I click about again, then it shows
/about
So when I click about it changes the page visually on my screen, but the console logs /, so in order for the navbar to change colors, I have to click about again, which makes no sense. I pretty much have to click it twice to update the color.
Why doesn't the window.location.pathname automatically update to the page I am on instead of showing the previous link I clicked, then only after I click the link again it shows the correct path?
Also, I am using Gatsby JS and have AOS animations, but I don't think that matters.
Update: If I pass it into a useEffect hook, it shows / then /about, but why doesn't it only show me /about instead of showing both the previous and new link I clicked?
useEffect(() => {
updateNavbar()
}, [])

Your console print before your route is rendering!
So you can use:
setTimeout(function(){ console.log(window.location.pathname) }, 3000);
to print or get the path after your route change.

I suspect that code isn't working because you are setting your onClick function at the same time that it navigates to another page. What could work for you could be:
const updateNavbar = (destination) => {
if (typeof window !== 'undefined' && window.location.pathname !== "/") {
setNavbar(true)
navigate("destination")
} else if(typeof window !== 'undefined') {
setNavbar(false)
navigate("/")
}
console.log(window.location.pathname)
}
<NavLink key={index} onClick={updateNavbar(item.link)}>
{item.title}
</NavLink>
Basically, you are preventing the code to navigate to your page before it set the state. Changing the native to prop to navigate function.
Another workaround that may work for you is using the state from Gatsby <Link> component:
<Link
to={`/your/page`}
state={{ navbar: true }}
>
You can retrieve the value using: props.location.state.navbar.
I've added it to give you an approach. On the other hand, the code above will break in the build mode (gatsby build) since you are using window (a global object) and, since gatsby build occurs in the server, where there's no window, it will break the compilation. You should wrap every usage of window inside:
if (typeof window !== `undefined`) {
// your stuff
}
However, there's a more native approach. Since Gatsby extends from React (and from #react/router) it exposes at the top-level component (pages) a prop named location with all your desired information. Of course, you can pass downside from the page component to any atomized component to use it.
After some trials and errors we solved the issue by:
const updateNavbar = () => {
if (window.location.pathname !== "/") {
setNavbar(true)
} else {
setNavbar(false)
}
}
useEffect(() => {
if (window.location.pathname) {
setNavbar(window.location.pathname)
}
console.log(window.location.pathname)
}, [])
background: ${({ navbar }) => (navbar != "/" ? "#000" : "transparent")};

Related

React scroll to element (ref current is null) problem

I have a problem I'm not able to solve. The app got a component where a do looping array and making multiple elements off it. Then I want to make buttons in another component that will scroll to a specific element. (something similar to liveuamap.com when you click on a circle).
I tried the below solution, but got "Uncaught TypeError: props.refs is undefined". I could not find any solution to fix it.
The second question: is there a better or different solution to make scrolling work?
In app component I creating refs and function for scrolling:
const refs = DUMMY_DATA.reduce((acc, value) => {
acc[value.id] = React.createRef();
return acc;
}, {});
const handleClick = (id) => {
console.log(refs);
refs[id].current.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
The refs I send to the article component as a prop where I render elements with generated refs from the app component.
{props.data.map((article) => (
<ContentArticlesCard
key={article.id}
ref={props.refs[article.id]}
data={article}
onActiveArticle={props.onActiveArticle}
activeArticle={props.activeArticle}
/>
))}
The function is sent to another component as a prop where I create buttons from the same data with added function to scroll to a specific item in the article component.
{props.data.map((marker) => (
<Marker
position={[marker.location.lat, marker.location.lng]}
icon={
props.activeArticle === marker.id ? iconCircleActive : iconCircle
}
key={marker.id}
eventHandlers={{
click: () => {
props.onActiveArticle(marker.id);
// props.handleClick(marker.id);
},
}}
></Marker>
))}
Thanks for the answers.
Ok so i found the solution in library react-scroll with easy scroll implementation.

How do I conditionally render elements based off of path react.js

So I'm creating a blog app with react.js and ruby on rails. I have these buttons that are in my nav component that I need to conditionally render based off of the path the user is in. I'm using useLocation to accomplish this and almost have it where I want it. The problem I'm having is getting them to render in the three main paths where posts can be seen ('/general', '/resources', & '/events') while hiding them when a user goes into a post to view the comments. The buttons will show up in those paths if I remove the /path/id but as I stated I need them not to render in the /path/id just the /path. How can I accomplish this?
const [getAllPosts, setGetAllPosts] = useState([])
useEffect(() => {
const fetchData = async () => {
const res = await fetchAllPosts()
setGetAllPosts(res.filter(post => {
return post.category.title === 'general' || post.category.title === 'resources' || post.category.title === 'events'
}))
}
fetchData()
}, [])
return (
{getAllPosts.forEach((generalPost, resourcePost, eventsPost) => {return location.pathname === '/user' || location.pathname === '/about' || location.pathname === '/create' || location.pathname === `/general/${generalPost.id}` || location.pathname === `/general/${resourcePost.id}` || location.pathname === `/general/${eventsPost.id}` ? (<></>) : (<div className={open ? 'RegisterContainerMobileOpen' : 'RegisterContainerMobile'}>
{currentUser ? (
<button onClick={handleLogout} className='logoutmobile'>Logout</button>
) : (
<Link to='/user' className='resgisterlinkmobile'>
<button className='registermobile'>Signin/Signup</button>
</Link>
)}
</div>)})})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
If you need to see the rest of my code just let me know. I've been working day and night on this with little sleep so I feel I am missing something simple.
Edit: I've also tried using .map() but get back multiple instances of the same button since map returns a new array. I've looked into React.memo in my research, but I'm not fully sure how I'd use that or even if it would be a fix to the mapping issue
Note: after reading more documentation on React.memo it does not seem like that would help
Without going too deeply into the task, try to add return and instead of forEach use map. Remember that forEach returns nothing, unlike map.
So it could look like this:
useEffect(() => {
...
}
return getAllPosts.map(...

Is there any alternative to the used method 'scroll top' when a link is clicked?

When a user clicks a link that directs the user to a new page, it generally put the user's view in the middle of the new page, at the same position as the original link. To prevent this, we can use the well-known method; scrolling up from window events.
I would like to know if there are any alternatives or tips that will prevent the user from seeing the scrolling up. Ideally, I would like the view to be at the top straight away like a new open page.
Thank you,
I found the following solution in my case to behave like a new page:
const ParentComponent: React.FC = () => {
const [isViewTopPage, setViewTopPage] = useState(false);
const scrollToTheTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
let result = window.scrollY === 0;
setViewTopPage(result);
};
useEffect(() => {
// If the user refresh the page
if (window.scrollY === 0) {
setViewTopPage(true);
}
// User coming from the link clicked
if (!isViewTopPage) {
window.addEventListener('scroll', scrollToTheTop);
}
return () => window.removeEventListener('scroll',
scrollToTheTop);
}, [isViewTopPage]);
return (
<section id="parent-component">
{/* Your conditional rendering *}
{isViewTopPage && (
<Header/>
<YourChildComponent/>
<Footer/>
)}
</section>
);
};
Note: as my child component was not too down from the top viewport, the scrolling up was very fast. It might not be the case for you if your child component is too down. You might have to implement an animation while scrolling up for the user experience.

useEffect not applying classNames

I'm currently working on a project using Next JS, where I seem to have encountered an issue with React. Here is the simplified version of my code. I did try to replicate the issue in codesandbox but I couldn't. I'll keep trying though and if I can, I'll update this post with the link.
const Nav = React.forwardRef<
HTMLDivElement,
{ className?: string; disableAnimation?: boolean }
>((props, ref) => {
const navWrapperRef = useRef<HTMLDivElement>(null);
const navItemsRef = useRef<HTMLSpanElement[]>([]);
useEffect(() => {
const path = window.location.pathname;
if (path === "/") navItemsRef.current[0].classList.add("nav-active");
else if (path.includes("/packages"))
navItemsRef.current[1].classList.add("nav-active");
else if (path.includes("/bhutan"))
navItemsRef.current[2].classList.add("nav-active");
}, []);
return (
<nav className={props.className || "nav"} ref={navWrapperRef}>
<div className="nav-container">
<ul className="nav-ul">
{navLinks.map((link) => (
<button
key={uniqueId(`nav-links-${new Date().getUTCDate()}`)}
data-name={link.name}
className="nav-links"
>
<span
ref={(el) => navItemsRef.current.push(el as HTMLSpanElement)}
className="nav-span"
>
<Link href={link.href}>{link.name}</Link>
</span>
</button>
))}
</ul>
</div>
</nav>
);
});
My objective here is to implement a navigation component without the use of states. I'd like to render out the current active navigation link on the initial page load using the empty dependency array as for the useEffect hook. But I can't seem to get it to work.
My desired output is the following on page load:
The output I get:
However, if I remove the dependency array altogether then all seems to work fine as expected. But if I am not wrong I think this causes performance issues as it re-renders each and every time if there are any other state changes. Any help would be greatly appreciated!
The contents of your useEffect hook will run once on mount and whenever its dependencies change.
As this is reliant on what you have defined as path, I'd move it out of the useEffect and add it as a dependency.
Update: you will have to use next/router's useRouter hook instead of the window directly when working with next.
Demo here.
const { asPath } = useRouter();
useEffect(() => {
if (asPath === "/") navItemsRef.current[0].classList.add("nav-active");
else if (asPath.includes("/packages"))
navItemsRef.current[1].classList.add("nav-active");
else if (asPath.includes("/bhutan"))
navItemsRef.current[2].classList.add("nav-active");
}, [asPath]);

useEffect not cleaning up after redirect

Let me explain my problem I have been solving for all day.
I have a site with header which has of course by react-router links to other pages (home, projects, about, services, contact).
Have a Project component which is in '/projects' page and '/' (home) page.
I want to make a simple animation in Project.js component which depends if there is a 'vertical' or there is not this props. Clearly -> in '/projects' I want to do that animation on scroll - in other pages not.
Tried to do that by add if statement in useEffect but it's not working, get me an error 'cannot read property 'style' of null ref.current.style.transform = `translateY(${window.scrollY * -0.35}px)`;
This problem is showing up when I am changing pages in header i.eg. I am in '/projects' scrolling and is ok animation is working then go to '/' and when scroll got error I have showed above.
It is like my if statement is not working and when I am in '/' which Project component has props vertical={false} is making animation on scroll when I don't want to do that.
What I want? I want do make an animation using useEffect only if component has a props 'vertical' like this:
Project.js component code:
const Project = ({ image, className, vertical }) => {
const ref = useRef(null);
const [isVertical, setIsVertical] = useState(vertical);
useEffect(() => {
console.log('component did mount');
isVertical
? window.addEventListener('scroll', () => {
ref.current.style.transform = `translateY(${window.scrollY * -0.35}px)`;
})
: console.log('non-vertical');
}, [isVertical]);
useEffect(() => {
return () => console.log('unmount');
});
return <StyledProject image={image} className={className} vertical={vertical} ref={ref} />;
};
in home '/':
{images.map(({ image, id }) => (
<Project key={id} image={image} />
))}
in '/projects':
{images.map(({ image, id }) => (
<StyledProject vertical image={image} key={id} />
))}
when I am in the path '/projects' and go to another path got error.
It is like after being in '/projects' it is saving all statements was but I want on every page reset useEffect and ref.current
Please help me, I can't go further since I don't fix this.
Thanks in advance.
Main problem is that you are not removing event listener when component unmounts.
Here you can see an example how to do it.

Resources