Here is the video proof.
https://dsc.cloud/leonardchoo/Screen-Recording-2022-03-08-at-17.25.58.mov
I'm running into a mysterious error where I click the button for navigation, "onClick" event is fired but it does not redirect and render the target component.
As you can see in the screenshot, the onClick event is logged, but the redirect does not happen.
I reproduced the situation here in CodeSandbox.
Stack
React TS
Mantine UI
React Router V5
How can I solve this issue?
First thing I noticed in your code was that is is rendering a WrapperPage component around each routed component with the navigation logic. I tried simplifying the WrapperPage code as much as possible.
Steps Taken:
Refactored the header and navbar props into standalone components in case there was issue generating JSX
Wrapped the Switch component in App with a single WrapperPage instead of each routed component
The issue persisted.
I next removed the UnstyledButton from #mantine/core so only the Link components were rendered, and could not reproduce. I then tried vanilla HTML buttons instead of the UnstyledButton and they again reproduced the issue.
So it seems it is an issue with rendering an interactive element (i.e. anchor tag from Link) within another interactive element (i.e. button from UnstyledButton) that is an issue. Swapping the element order, i.e. Link wrapping the UnstyledButton, appears to reduce the issue. I can't seem to reproduce the issue with the DOM structured this way.
Header
const CustomHeader = ({
opened,
setOpened
}: {
opened: boolean;
setOpened: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const theme = useMantineTheme();
return (
<Header height={70} padding="md">
{/* Handle other responsive styles with MediaQuery component or createStyles function */}
<div style={{ display: "flex", alignItems: "center", height: "100%" }}>
<MediaQuery largerThan="sm" styles={{ display: "none" }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray[6]}
mr="xl"
/>
</MediaQuery>
<Group>
<ThemeIcon variant="light" color="orange">
🎙
</ThemeIcon>
<Text>Mantine AppShell with React Router</Text>
</Group>
</div>
</Header>
);
};
Navbar
const CustomNavbar = ({ opened }: { opened: boolean }) => {
const location = useLocation();
const { classes } = useStyles();
return (
<Navbar
padding="md"
// Breakpoint at which navbar will be hidden if hidden prop is true
hiddenBreakpoint="sm"
// Hides navbar when viewport size is less than value specified in hiddenBreakpoint
hidden={!opened}
// when viewport size is less than theme.breakpoints.sm navbar width is 100%
// viewport size > theme.breakpoints.sm – width is 300px
// viewport size > theme.breakpoints.lg – width is 400px
width={{ sm: 300, lg: 400 }}
>
<Link
to="/dashboard"
className={classes.link}
>
<UnstyledButton
className={
location.pathname === "/dashboard"
? classes.button_active
: classes.button
}
>
<Group>
<ThemeIcon variant="light">
<DashboardIcon />
</ThemeIcon>
<Text size="sm">Dashboard</Text>
</Group>
</UnstyledButton>
</Link>
<Link
to="/new-recording"
className={classes.link}
>
<UnstyledButton
className={
location.pathname === "/new-recording"
? classes.button_active
: classes.button
}
>
<Group>
<ThemeIcon variant="light" color="red">
<RadiobuttonIcon />
</ThemeIcon>
<Text size="sm">New Recording</Text>
</Group>
</UnstyledButton>
</Link>
<Link
to="/calendar"
className={classes.link}
>
<UnstyledButton
className={
location.pathname === "/calendar"
? classes.button_active
: classes.button
}
>
<Group>
<ThemeIcon variant="light" color="orange">
<CalendarIcon />
</ThemeIcon>
<Text size="sm">Calendar</Text>
</Group>
</UnstyledButton>
</Link>
</Navbar>
);
};
WrapperPage
const WrapperPage = ({ children }: Props): JSX.Element => {
const [opened, setOpened] = useState(false);
return (
<AppShell
// navbarOffsetBreakpoint controls when navbar should no longer be offset with padding-left
navbarOffsetBreakpoint="sm"
// fixed prop on AppShell will be automatically added to Header and Navbar
fixed
header={<CustomHeader opened={opened} setOpened={setOpened} />}
navbar={<CustomNavbar opened={opened} />}
>
{children}
</AppShell>
);
};
Related
I have this Next.js / React component:
const AppBarContent = (props: Props) => {
// ** Props
const {
horizontalAppBarContent: userHorizontalAppBarContent,
horizontalAppBarBranding: userHorizontalAppBarBranding,
} = props
// ** Hooks
const theme = useTheme()
return (
<div
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
{userHorizontalAppBarBranding ? (
userHorizontalAppBarBranding(props)
) : (
<Link href="/">
<Image src={'/images/logo.svg'} width={48} height={48} />
</Link>
)}
Got this warning:
next-dev.js?3515:25 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? Check the render method of ForwardRef.
If I put const AppBarContent = React.forwardRef((props: Props, ref) => { around component as one suggest: https://stackoverflow.com/a/66423250/239219
I still get the error, but if I but between and warning disappear.
<Link href="/">
<a>
<Image src={'/images/logo.svg'} width={48} height={48} />
</a>
</Link>
what can I do. I think my solution is not based on Next.js doc, it is a shortcut. But the proper solution does not work. Why?
return (
<HorizontalLayoutWrapper className="layout-wrapper">
{/* Navbar (or AppBar) and Navigation Menu Wrapper */}
<AppBar
color="default"
elevation={skin === 'bordered' ? 0 : 3}
className="layout-navbar-and-nav-container"
position={appBar === 'fixed' ? 'sticky' : 'static'}
>
{/* Navbar / AppBar */}
<div className="layout-navbar">
<div className="navbar-content-container">
<AppBarContent
I have antd Menu inside collapsible Sider. I've set default open keys for one of Submenus and they should open one at a time. Here is my code for the Menu:
const MainMenu = ({ defaultOpenKeys }) => {
const [openKeys, setOpenKeys] = useState(defaultOpenKeys);
const rootKeys = ["sub1", "sub2"];
// Open only one submenu at a time
const onOpenChange = props => {
const latestOpenKey = props.find(key => openKeys.indexOf(key) === -1);
if (rootKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(props);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : defaultOpenKeys);
}
};
return (
<Menu
theme="dark"
openKeys={openKeys}
defaultSelectedKeys={["1"]}
mode="inline"
onOpenChange={onOpenChange}
>
<Menu.Item key="1">
Option 1
</Menu.Item>
<SubMenu key="sub1" title="User">
<Menu.Item key="2">Tom</Menu.Item>
</SubMenu>
<SubMenu key="sub2" title="Team">
<Menu.Item key="3">Team 1</Menu.Item>
</SubMenu>
</Menu>
);
};
export default MainMenu;
I pass defaultOpenKeys from the Sider.
const SiderDemo = () => {
const [collapsed, setCollapsed] = useState(false);
const toggleSider = () => {
setCollapsed(!collapsed);
};
return (
<Layout style={{ minHeight: "100vh" }}>
<Button type="primary" onClick={toggleSider}>
{React.createElement(
collapsed ? MenuFoldOutlined : MenuUnfoldOutlined
)}
</Button>
<Sider
collapsible
collapsed={collapsed}
collapsedWidth={0}
trigger={null}
>
<Menu defaultOpenKeys={["sub1"]} />
</Sider>
...
</Layout>
);
};
It works on mount, but when I collapse the Sider, defaultOpenKeys are being reset. How can I keep defaultOpenKeys from being reset, when the Sider is collapsed?
I have created a codesandbox and added console log in the Menu. You can see that defaultOpenKeys and openKeys are the same on mount. When I collapse the Sider, the console log is triggered twice. The first time defaultOpenKeys and openKeys are the same. And the second time openKeys become empty. How can I fix that?
Reason: on closing the sidebar it is closing opened sidemenu so it gonna trigger openchange with empty array and hence your logic making it reset to empty.
Here is code sandbox link with updated code
https://codesandbox.io/s/sider-demo-0der5?file=/Menu.jsx
Suggestion: Its anti pattern to assign props to initial state. if prop value changed in parent component then the new prop value will never be displayed because intial state will never update the current state of the component. The initialization of state from props only runs when the component is first created
I am using react-router and material-ui. I have a BottomNavigation and BottomNavigationAction (MaterialUI) (with Link component from Reach Roouter ).
import { Link } from "#reach/router"
const useStyles = makeStyles((theme) => ({
myButtomNavigation: {
color: '#000',
'&$selected': {
paddingTop: '16px',
},
},
selected: {
},
}));
export default function Navigation(props) {
const [value, setValue] = React.useState('INIT');
const classes = useStyles();
const handleChange = (event, newValue) => {
setValue(newValue);
};
return(
<BottomNavigation value={value} onChange={handleChange}>
<BottomNavigationAction showLabel={false} value="INIT" icon={<HomeOutlinedIcon />} component={Link} to="/" classes={{root: classes.myButtomNavigation, selected: classes.selected}} />
<BottomNavigationAction showLabel={false} value="ANIMALS" icon={<PetsOutlinedIcon />} component={Link} to="/animals" classes={{root: classes.myButtomNavigation, selected: classes.selected}}/>
<BottomNavigationAction showLabel={false} value="ROUTINE" icon={<NotificationsOutlinedIcon />} component={Link} to="/activities" classes={{root: classes.myButtomNavigation, selected: classes.selected}} />
<BottomNavigationAction showLabel={false} value="SETTINGS" icon={<SettingsOutlinedIcon />} component={Link} to="/configuration" classes={{root: classes.myButtomNavigation, selected: classes.selected}} />
</BottomNavigation>
);
}
I have a problem to know how to get the current active Link and set it to the value of the BottomNavigation when someone entered a different url than the root path from the browser.
When i enter with the root path (localhost:3000/): all is work (because i set value to root path).
When i enter with other path, like localhost:3000/animals i need to set the correct value so the right icon show active.
Reach Router gives a possiblity in Link component: passing a function to getProps that you can return a style for the current active link but the style affect anchor element and i need to affect an other object (https://reach.tech/router/example/active-links).
How can i set the value of bottomNavigation according to the current active link when someone entered path from browser (and is not the rootpath)??
In react-router-dom I am using NavLink with property (activeClassName="current") instead of Link, maybe there is something similar in Reach Router.
I'm following this code, which allow use some routes inside your modal, this works ok although the code is a bit unclear to me,
https://reacttraining.com/react-router/web/example/modal-gallery
basically I understand that before clicking in some modal link we save the location, also we toggle some backgroud state which allow to our routes know that the modal must be displayed, our routes then get the previous route and render that component (which would be the page where the link to our modal live) and render the modal (because the background state is not null)
so far so good, but now I need move between modals, basically I need display some modal with different links inside it (pointing to another modal) and when the user click some link keep the modal open and display the new content
not sure how create a fork from this code
https://reacttraining.com/react-router/web/example/modal-gallery
but you can replace the code inside that windows with it and get the idea
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useHistory,
useLocation,
} from 'react-router-dom';
// This example shows how to render two different screens
// (or the same screen in a different context) at the same URL,
// depending on how you got there.
//
// Click the "featured images" and see them full screen. Then
// "visit the gallery" and click on the colors. Note the URL and
// the component are the same as before but now we see them
// inside a modal on top of the gallery screen.
export default function ModalGalleryExample() {
return (
<Router>
<ModalSwitch />
</Router>
);
}
function ModalSwitch() {
const location = useLocation();
// This piece of state is set when one of the
// gallery links is clicked. The `background` state
// is the location that we were at when one of
// the gallery links was clicked. If it's there,
// use it as the location for the <Switch> so
// we show the gallery in the background, behind
// the modal.
const background = location.state && location.state.background;
console.log('brackground is', background);
console.log('background is ', background);
return (
<div>
<Switch location={background || location}>
<Route path="/" children={<Gallery />} />
</Switch>
{/* Show the modal when a background page is set */}
{background && <Route path="/modal1" children={Modal1(Hello)} />}
{background && <Route path="/modal2" children={Modal1(Bye)} />}
</div>
);
}
function Gallery() {
const location = useLocation();
console.log('location inside Gallery ', location);
return (
<div>
<div>
<Link
to={{
pathname: '/modal1',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
hello Modal
</Link>
<div>
<Link
to={{
pathname: '/modal2',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
bye Modal
</Link>
</div>
</div>
</div>
);
}
function Hello() {
const location = useLocation();
console.log('location inside hello ', location);
return (
<>
<div>Hola</div>
<div>
<Link
to={{
pathname: '/modal2',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
GO TO MODAL 2
</Link>
</div>
</>
);
}
function Bye() {
const location = useLocation();
console.log('location inside bye ', location);
return (
<>
<div>Modal2: Bye</div>
<div>
<Link
to={{
pathname: '/modal1',
// This is the trick! This link sets
// the `background` in location state.
state: { background: location },
}}
>
GO TO MODAL 1
</Link>
</div>
</>
);
}
function Modal1(component) {
const history = useHistory();
const back = e => {
e.stopPropagation();
history.goBack();
};
return (
<div
onClick={back}
style={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
background: 'rgba(0, 0, 0, 0.15)',
}}
>
<div
className="modal"
style={{
position: 'absolute',
background: '#fff',
top: 25,
left: '10%',
right: '10%',
padding: 15,
border: '2px solid #444',
}}
>
rendered
{component()}
<button type="button" onClick={back}>
Close
</button>
</div>
</div>
);
}
right now my possible solution is use redux and save the current page in the state, when the user moves through the modals keep that state intact so I keep displaying the background component, and the link must be the modal link...but I'd like to know if is there some great solution using plain hooks and that doesnt require redux?...thank you
NOTE: please let me know if there is a more clear question title and I'll change this
thank you so much
I am adding the slider menu react-native-drawer-menu to my app in app.js
<Drawer
style={styles.container}
drawerWidth={300}
drawerContent={drawerContent}
type={Drawer.types.Overlay}
customStyles={{drawer: styles.drawer}}
drawerPosition={Drawer.positions.Left}
onDrawerOpen={() => {console.log('Drawer is opened');}}
onDrawerClose={() => {console.log('Drawer is closed')}}
easingFunc={Easing.ease}
>
<Provider store={store}>
<MenuContext>
<RootContainer />
</MenuContext>
</Provider>
</Drawer>
In root container I create the app-navigator and load the first view WelcomeContainer. Every thing is working fine and I am able to swipe in welcomeContainer to open the menu.
Now I tried to also open the menu when I click on the Menu Icon on the header. The header is created on the WelcomeContainer. This is the code
render() {
const {language} = this.props
const {navigate, setParams} = this.props.navigation
return (
<Drawer ref={drawer => this.drawer = drawer}>
<View style={ styles.container }>
<Header
leftComponent = {
{
icon: 'menu',
color: '#5c72b0',
onPress: () => this.drawer.openDrawer()
}
}
centerComponent={<ChangeLanguage />}
rightComponent={<HeaderUserInformation />}
outerContainerStyles={{ backgroundColor: '#eee9e9' }}
/>
Unfortunately this dose not work properly. This is how the slider menu looks like:
So swiping is working fine but not manually opening the menu. Do you have any idea about the problem? I have the feeling that by clicking, I am opening the menu on the wrong reference of drawer, but I am not able to locate the problem.