React: Conditional className is not updated in DOM - reactjs

When conditions changes (location) react doesn't update the classname in dom, although it looks changed in react developer tools. Simplified code is something like this:
<Menu>
{appRoutes.map(route => {
console.log(
pathname,
route.path,
pathname === route.path,
pathname === route.path ? 'ant-menu-item-selected' : '',
);
return (
<Menu.Item
key={route.name}
className={pathname === route.path ? 'ant-menu-item-selected' : ''}
onClick={() => {
setOpenKeys([getKey(route.name, index)]);
if (app.mobile) app.toggleMobileDrawer();
}}
>
<Link to={route.path}>
<span>
<span className="mr-auto">{capitalize(route.name)}</span>
</span>
</Link>
</Menu.Item>
);
})}
</Menu>
pathname variable is from location. I use useLocation hook from react router so when location changes, component re-renders properly. console.log prints all vars as it should be. Further, when I check react developer tool, classname looks as it should be:
But when checking the elements in developer tools, class is not updated for the same element in dom:
When I refresh the page (not changing the location), it renders properly, and class name is removed from dom. So how can I force react to update the classname in dom?
Using react 16.13.1, antd components.

One approach is to use the selectedKeys property from Antd Menu and set this to your current route path value.
selectedKeys: Array with the keys of currently selected menu items
The menu may look like this:
<Menu
activeKey={props.currentPath}
mode="inline"
selectedKeys={props.currentPath}
style={{ height: "100%", borderRight: 0 }}
>
Additional examples can be found in antd's github thread for >how to use sider with react-router links<.

Related

How to get current hash route in single page website

I have a single-page web portfolio in which I have a navbar like the following code:
const Nav = ({ isToggled, onToggle }) => {
const [activeNav, setActiveNav] = useState('#')
return (
<nav>
<a
href='/#'
onClick={() => setActiveNav('#')}
className={activeNav === '#' ? 'active' : ''}
>
<AiOutlineHome />
</a>
<a
href='#about'
onClick={() => setActiveNav('#about')}
className={activeNav === '#about' ? 'active' : ''}
>
<AiOutlineUser />
...
</nav>
)
}
I have hyperlink tags to navigate through the website. However, I would like to update the active nav icon according to the current place the user is in. I found something called useLocation() from react-router-dom, but I currently do not use this package. Is it necessary? What should I do to achieve my goal? Thanks in advance for any help you can provide.
A really simple solution I could think of would be combining the use ScrollTrigger plugin from GSAP and React useEffect, they have really great documentations on their site! But this is the most basic example, say if you just want the nav link to show an active state if the section enters the viewport
useEffect(() => {
gsap.to(".nav-link-className", {
scrollTrigger: "#id",
// active state styling here
});
})
EDIT:
Since I do not use GSAP myself that often, there may be a much better way of going about this within GSAP, but here is an example on CodeSandBox, this should at least get you started. Note that one limitation (or at least my own limitation due to lack of knowledge with GSAP) is that I would have to hard code the animation when the trigger element leaves the DOM
The solution I found was to use the react-scroll package. I just switched my a tags for Link from react-scroll.
This:
<a
href='/#'
onClick={() => setActiveNav('#')}
className={activeNav === '#' ? 'active' : ''}
>
<AiOutlineHome />
</a>
For this:
<Link
activeClass='active'
to={'header'}
spy={true}
duration={500}
offset={-100}
>
<AiOutlineHome />
</Link>
In App.jsx, wrapped each component in Element from react-scroll (e.g. <Element name={'portfolio'}><About /></Element>).

Ignore initial Transition for first mount React-Spring

I have a react component which needs a transition for for in and out.
But on the first mount it should not use an entry animation.
I use a simple fade in fade out. the initial keyword should deactivate the initial transition for the first mount. But it does not work. The following transitions work as aspected.
I tried to find a solution but most of the topics were outdated or did not work for me.
Maybe I am missunderstanding something since I am pretty new to React & React-Spring.
<Transition
native
items={this.state.showComponent}
initial={null}
from={{opacity:0}}
enter={{opacity:1}}
leave={{opacity:0}}
>
{show => show && (props =>
<animated.div style={props}>
//Component content
</animated.div>
)}
</Transition>
If you do not want to see the initail transition you should introduce a flag for it. And based on the flag you can change the from property of the transition. The flag could be either a class variable or a state variable. For example:
class MyComponent extends React.Component {
initialised = false;
componentDidMount {
initialised = true;
}
...
<Transition
native
items={this.state.showComponent}
initial={null}
from={{opacity: this.initialised ? 0 : 1}}
enter={{opacity:1}}
leave={{opacity:0}}
>
{show => show && (props =>
<animated.div style={props}>
//Component content
</animated.div>
)}
</Transition>

Can't get Popover to display in correct position in Dialog

I have a Dialog and have a ListItem that when you click on it goes into edit mode by showing a Popover. This was working in an older version of MUI using a Modal but since getting on the latest that didn't work and I'm trying to use a Popover. I tried to make a simple example on CodeSandox but that works. What happens is the Popover is always in the upper left of the page instead of the ListItem.
I have simplified my code to a simple Button and Popover in the Dialog and still have the same problem and have ran out of ideas on what to try next. The error I get in the console is
[Warning] Material-UI: the `anchorEl` prop provided to the component is invalid.
The anchor element should be part of the document layout.
Make sure the element is present in the document or that it's not display none.
When the item is clicked I do event.currentTarget just like in the examples and this is what the console.log looks like for it.
[Log] <button class="MuiButtonBase-root MuiButton-root MuiButton-text" tabindex="0" type="button"> (main.chunk.js, line 26437)
<span class="MuiButton-label">Click Me</span>
<span class="MuiTouchRipple-root">
<span class="MuiTouchRipple-ripple MuiTouchRipple-rippleVisible" style="width: 117.2006825918689px; height: 117.2006825918689px; top: -34.60034129593445px; left: -25.60034129593445px;">
<span class="MuiTouchRipple-child MuiTouchRipple-childLeaving"></span>
</span>
</span>
</button>
I even tried doing disablePortal in the Dialog which didn't fix it. I also tried using refs which fixed the anchorEl warning but still displays relative to the page and not the element. Any ideas?
For anyone that comes across this issue with Material UI, there are a couple of things that you can do.
One is to make sure that if you have multiple nested functional components, that your anchorEl / click handlers for the popover are defined within the specific functional component that holds the popover. If you have nested functional components and the parent component holds the state, it will rerender the children on every state change, which can reset the anchorEl reference.
Second - React.memo can prevent unnecessary rerenders on functional components (it only works if props don't change but should still reap performance benefits in child components).
I have nested elements this is how I solved this without doing anything too extra.
So my main functional component simply returned something like this
const filters = () => {
const [anchorEl, setAnchorEl] = useState(null)
const popoverOpen = Boolean(anchorEl)
const handleTogglePopover = (event: any) => {
event.preventDefault()
if (anchorEl === null) {
setAnchorEl(event.currentTarget)
} else {
setAnchorEl(null)
}
}
const SortActions = () => {
return (
<Box>
<MyCustomRadioButton/>
</Box>
)
}
const FilterButtons = () => {
return (
<Box>
<ButtonBase
onClick={handleTogglePopover}
aria-owns={popoverOpen ? 'my-popover-id-name' : undefined}
aria-haspopup={true}
>
{/* contents (this is a comment in html in react) */}
</ButtonBase>
<Popover
id={'my-popover-id-name'}
open={popoverOpen}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
onClose={handleTogglePopover}
>
<SortActions/>
</Popover>
</Box>
)
}
return (
<Box>
{/* THIS LINE IS THE LINE I CHANGED TO GET IT TO WORK */}
<FilterButtons/>
</Box>
)
}
I changed that line to {FilterButtons()} instead. It looks like the component that is rendering the popover needs to exist within the functional component calling it.
However any nested components under do not need to be declared within the calling functional component.
From what I have gathered in looking at many peoples solutions is to use React.memo for this but this worked for me. Something about React re-rendering the popover losing the state when its called as a nested component rather than a function within the component causes the state to be lost? I assume it has to do with how JavaScript works in terms of encapsulation within a function.
I know this is an older question but I know people will still run by this question eventually.
It's also possible to get this error due to what it's saying - you might be trying to use an element that has display: none style as an achorEl for your component, which isn't supported as the underlying logic calculating the position of the anchor element needs it to be visible on screen.
Check if there is any display: none; style
May be anchorEl used in multiple nested functional components problem
Try to use memo concept to prevent component rerender

React tabs - switching destroys component, need to maintain the same component

I am trying to make a multi-tabbed SPA with React and Material-UI. I use code from this demo as an example: https://codesandbox.io/s/qlq1j47l2w
It appears that if I follow the aforementioned example, I end up returning new instance of the component with tab contents each time I navigate between the tabs:
<Tabs
value={value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
scrollable
scrollButtons="auto"
>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
</Tabs>
</AppBar>
{value === 0 && <Tab1Contents/>}
{value === 1 && <Tab2Contents/>}
As Tab1Contents is a form, I would like its internal state to be retained, instead of loading a new instance of the component, which the code above appears to do.
What is the best way to get React to use only one instance of the component and 'memorise field values'?
EDIT
I have added Redux to the example, but the store corresponding to the form within the Tab is destroyed the moment I switch away. Is there any other way to implement tabs in React that would hide the tab contents, instead of destroying them and re-creating them from scratch each time I navigate away?
The solution to my problem was quite simple! If you don't want to destroy the component (remove it from DOM), you can simply hide it!
Instead of:
{value === 0 && <Tab1Contents/>}
Use:
<div style={{ display: value === 0? 'block': 'none'}}>
<Tab1Contents/>
</div>
It was already mentioned that you have to prevent your component from being removed from the DOM. The easiest solution with MUI5 TabPanel is in fact to just replace
{value === index && children}
with
{children}
that means your Tabpanel would look like that:
import * as React from "react";
const TabPanel= ({ children, value, index, ...other }) => {
return (
<div
role="tabpanel"
hidden={value !== index}
id={`tabpanel-${index}`}
aria-labelledby={`tab-${index}`}
{...other}
>
{children}
{/* {value === index && children} // This was removed */}
</div>
);
};
export default TabPanel;
No additional logic necessary as the hidden prop already takes care of the visibility aspect. This way your components should maintain their state!
You would need to persist the state between the tab changes. I prototyped a form using React forms documentation for Tab1Container and as you play around with it, the value will disappear
https://codesandbox.io/s/material-demo-ojwhr
What you ideally need to use something like Redux, which will use a store to keep the information even between the state changes like Tab clicks.
Hope this helps!

Unable to make React Router Dom Link to work

In my react component, PostItem, I've created a pointing to edit post page. All the works in my other components, except this one, and I could not find out why.
The page is successfully rendered with tag and proper href address, but I can't click the link. In fact, none of tags or link works on this component.
I have tried to create direct and link to any external site, but all links seem to be disabled on this component.
const PostItem = ({ id, title}) => (
<div className="list-item prfx-color" key={id}>
<div className="list-header">
<h3 className="list-title">{title}</h3>
<Link to={`/EditPost/${id}`}>
<i className="material-icons" style={{ color: 'white' }}>edit</i>
</Link>
</div>
</div >
)
I expect links to be clickable.
I found the reason why the or any not working. It has nothing to do with React-Router-Dom. It's my sass, where parent has a attribute of "position: absolute" and "z-index: -1".
When working with z-index and position, it would disable all links on the page. So, what I did was simple delete the position attribute.

Resources