i'm new to react(hooks typescript) world, here i have an icon when user clicks that it should go to '/menu' and when it is again clicked it should go to previous page, and if again clicked it should go to '/menu' so basically it should work like toggle.
am i doing this wrong :
import { useHistory } from "react-router-dom";
const [menu, setMenu] = useState(false);
const history = useHistory();
<Box onClick={() => setMenu(!menu)}>
{menu ? (
<Link to={history.goBack}>
<MenuIcon fontSize="large" />
</Link>
) : (
<Link to="/menu">
<MenuIcon fontSize="large" />
</Link>
)}
</Box>
If I'm not wrong, Link can not be used this way. It expects the route not the function. How about using a div and placing onClick handler where you can navigate back through history.goBack()?
The to property expect a string as value (path). if you don't know what the previous route is, I recommend you to create a context to store the previous route value:
const RouterContext = React.createContext();
const RouterProvider = ({children}) => {
const location = useLocation()
const [route, setRoute] = useState({
to: location.pathname,
from: location.pathname
});
useEffect(()=> {
setRoute((prev)=> ({to: location.pathname, from: prev.to}) )
}, [location]);
return <RouterContext.Provider value={route}>
{children}
</RouterContext.Provider>
}
then:
const route = useContext(RouterContext);
<Box onClick={() => setMenu(!menu)}>
<Link to={menu ? route.from : "/menu" }>
<MenuIcon fontSize="large" />
</Link>
</Box>
Don't forget to add RouterProvider under Router component:
<Router>
<RouterProvider>
...
</RouterProvider>
</Router>
Working example (About component has the go back option)
Related
I'm trying to find a way to switch between dark and light theme in my web app.
I want to add the switch in my AppBar component which is inside my header but I'm not sure how to get it to work on all of the web app and not just the header.
AppBar.js :
//imports ...
const AppBar = () =>{
const [theme, setTheme] = useState(false);
const changeTheme = () => {
setTheme(!theme);
};
//rest of the code....
<Box sx={{ flexGrow: 0 }}>
<Switch checked={theme} onChange={changeTheme} />
</Box>
};
export default AppBar;
here is the code from the Header.js
const Header = () => {
return (
<header>
<AppBar />
</header>
);
};
export default Header;
so the header is just rendering one component one could get rid of it if it was necessary.
and here is my App.js (routes)
//imports ...
//themes ...
const app = ()=>{
return (
<React.Fragment>
<ThemeProvider theme={theme ? lightTheme : darkTheme}>
<CssBaseline />
<Router>
<Header />
<Routes>
//Routes ...
</Routes>
<Footer />
</Router>
</ThemeProvider>
</React.Fragment>
);
}
export default App;
I'd really appreciate the help and Thanks in advance!
you can store the value of theme inside context or redux store and change it using a dispatch function once that value changes the whole component related to it will re render !, so your component will change the value inside context ! rather than having the value stuck inside one component.
Hi I'm currenlty doing a side project for my portfolio and was wondering how did this error happen. It's a navbar component in react, with Link to other pages and button to set an active theme. Any Idea what is causing the infinite re-render? Thank you.
import React, {useEffect} from 'react'
import AppBar from '#mui/material/AppBar';
import Box from '#mui/material/Box'
import { Button } from '#mui/material';
import { Link } from 'react-router-dom';
const MenuBar = () => {
const [activeItem, setActiveItem] = React.useState('')
const handleItemClick = (e, {name}) => {
setActiveItem(name)
}
useEffect(() => {
const pathname = window.location.pathname
const path = pathname === '/' ? 'home' : pathname.substring(1)
setActiveItem(path)
}, [])
return (
<div>
<AppBar color='primary' position={'static'}>
<Box className='flex items-center justify-between'>
<Link to={'/'} onClick={setActiveItem('home')}>
<Button variant='text' sx={{ bgcolor: activeItem === 'home' ? '#FDFFA9' : '#FFD365'}}>Home</Button>
</Link>
<Box>
<Link to={'/login'} onClick={setActiveItem('login')}>
<Button variant='text'>Login</Button>
</Link>
<Link to={'/register'} onClick={setActiveItem('login')}>
<Button variant='text'>Register</Button>
</Link>
</Box>
</Box>
</AppBar>
</div>
)
}
export default MenuBar
I think the problem is with the way you assign onClick handlers
onClick={setActiveItem('home')}
during rendering you actually invoke the action, not assigning the callback, so
render -> setActiveItem -> render -> ..
please try to assign as the fn value instead:
onClick={() => setActiveItem('home')}
I have a Layout component which wraps the rest of my application. The default route is a page with multiple buttons, implemented as a small component called NavButton, which use history.push to go to a new route. Then in my Layout component, there are 2 buttons, but one of them should change depending on which route we are currently navigated to. So when we are on the main page, the button should say i.e. "Change Code", but on any other page, it should say "Back". I tried it in the following way:
Layout.tsx:
interface ILayoutProps {
children: ReactNode;
}
const Layout: React.FC<ILayoutProps> = ({ children }) => {
const currentRoute = useLocation();
useEffect(() => {
console.log(currentRoute);
}, [currentRoute]);
const logout = () => {
console.log('logout');
};
return (
<div>
<div>{children}</div>
<Grid container spacing={2}>
<Grid item xs={6}>
{currentRoute.pathname === '/' ? (
<NavButton displayName="Change Code" route="/change-code" variant="outlined" />
) : (
<Button variant="outlined" color="primary" fullWidth>
Back
</Button>
)}
</Grid>
...
</Grid>
</div>
);
};
export default Layout;
NavButton.tsx:
interface NavButtonProps {
displayName: string;
route: string;
variant?: 'text' | 'outlined' | 'contained';
}
const NavButton: React.FC<NavButtonProps> = ({ displayName, route, variant = 'contained' }) => {
const history = useHistory();
const navigatePage = () => {
history.push(route);
// Also tried it like this:
// setTimeout(() => history.push(route), 0);
};
return (
<Button
variant={variant}
color="primary"
onClick={() => navigatePage()}
fullWidth
>
{displayName}
</Button>
);
};
export default NavButton;
SO in my Layout, I am trying to keep track of location changes with the useLocation() hook, but when a button is pressed, and thus history.push(route) is called, it doesn't get detected by the hook in Layout. I've also tried to use the useHistory() hook and call the listen() method on it, but same problem. Also, my router is a BrowserRouter, not sure if that is of any help... What am I missing here?
So the issue was that I had my App component defined like this:
<Layout>
<Routes />
</Layout>
And my Routes looked like this:
<BrowserRouter>
<Switch>
<Route ...>
....
</Switch>
</BrowserRouter>
The useLocation() hook in my Layout wasn't working since it was not a child of my BrowserRouter. Changing it to be like that made the code work.
Only thing I find weird is that in my app, I didn't get an error for invalid hook call, but in the sandbox I did. Maybe I need to upgrade my React packages...
I'm using nextjs.
I'm opening host.domain/PageA?param1=one¶m2=two. On this page I've got a next/Link object to host.domain/PageB
is there an easier way to pass this parameters to to PageB than:
<Link href={`/PageB?param1=${Router.query.param1}¶m2=${Router.query.param2}`} />
something like
<Link passQueryString />
There's no native way to do that using Next.js, what you can do is to create a wrapper around Link to add that functionality.
import NextLink from "next/link";
const Link = ({ passQueryString, href, children, ...otherProps }) => (
<NextLink href={`${href}?${passQueryString}`} {...otherProps}>
{children}
</NextLink>
);
export default () => (
<div>
Hello World.{" "}
<Link href="/about" passQueryString="paramA=b¶mB=c">
<a>About</a>
</Link>
</div>
);
Also you can use query-string library to send an object and then stringify that object.
import NextLink from "next/link";
import queryString from "query-string";
const Link = ({ passQueryString, href, children, ...otherProps }) => (
<NextLink
href={`${href}?${queryString.stringify(passQueryString)}`}
{...otherProps}
>
{children}
</NextLink>
);
export default () => (
<div>
Hello World.{" "}
<Link href="/about" passQueryString={{ paramA: "a", paramB: "b" }}>
<a>About</a>
</Link>
</div>
);
Codesandbox
"Link can also receive an URL object and it will automatically format it to create the URL string"
so you can use instad of href="", this href={{ pathname, query }}
https://nextjs.org/docs/api-reference/next/link#with-url-object
import Link from 'next/link'
import { useRouter } from 'next/router'
const Index = () => {
const router = useRouter()
return (
<Link href={{ pathname: '/pageB', query: { ...router.query }}}>
<a>Test</a>
</Link>
)
}
or simply
//or <Link href={{ pathname: '/pageB', query: { params1: router.query.params1, params2: router.query.par } }}>
Example
I need to navigate to new link when the user clicks on the row items, the link is framed dynamically based on the data of that row, i am using react-data-table-component and they have a prop called onRowClicked ,I am able to get the data of the row that is being pressed but I am not able to navigate to new page any idea on how to navigate?
what i have tried is :
<DataTable
columns={columns}
data={rowData}
onRowClicked={rowData => (
<Redirect push to={`/admin/productspage/${rowData.shortName}`} />
)}
pointerOnHover={true}
highlightOnHover={true}
theme="light"
customStyles={customStyles}
striped={true}
/>
but it doesn't work.
Please Note : I do not have any history passed to component which is having react-data-table-component.
You're not able to be redirected because you need to render your <Redirect> in your code so it can work properly.
For functional components (be sure to import {useState} from 'React'):
const [redirState, setState] = useState(false);
const [shortName, setData] = useState('');
let redirecting = redirState ? (<Redirect push to={`/admin/productspage/${shortName}`}/>) : '';
return (
<DataTable
columns={columns}
data={rowData}
onRowClicked={rowData => {
setState(true);
setData(rowData.shortName);
}}
pointerOnHover={true}
highlightOnHover={true}
theme="light"
customStyles={customStyles}
striped={true}
/>
{redirecting}
);