Trying to update url-parameter with onClick in React - reactjs

I'm trying to setup a url that changes depending on the useState. I'm doing this because I want to be able to access both states with the url. So this is what my router file looks like:
import React from 'react';
import {BrowserRouter as Router, Switch, Route} from "react-router-dom";
function router(){
return (
<Router>
<Switch>
<Route path="/restaurant/:toggleParameter" children={<Restaurant/>}/>
</Switch>
</Router>
);
}
export default router;
An the component Restaurant looks like this:
import React, {useEffect, useState} from 'react';
import {useParams,Redirect} from "react-router-dom";
function RestaurantLandingPage(){
const {toggleParameter} = useParams();
console.log("Parameter");
console.log(toggleParameter);
const [profileToggle,setProfileToggle] = useState(toggleParameter);
const restaurantID =localStorage.getItem("restaurantId");
console.log(restaurantID);
const changeParameterToProfile =()=>{
setProfileToggle("profile");
};
const changeParameterToMenu=()=>{
setProfileToggle("menu");
}
return (
<div id="wrapper">
<div className="restaurantHover">
<button
className="switchButton"
onClick={()=>{changeParameterToProfile()}}
style={profileToggle==="profile"? {textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6C5AF2'}:{}}
>
Profile
</button>
<button
className="switchButton"
onClick={changeParameterToMenu}
style={profileToggle==="menu"?{textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6C5AF2'}:{}}
>
Menu
</button>
<div id="switchBottom"/>
{(profileToggle==="profile")&&(
<Contact profileToggle={profileToggle} changeParameterToMenu={changeParameterToMenu}/>
)}
{(profileToggle==="menu")&&(
<RestauarntMenuOverview/>
)}
</div>
</div>
)}
}
export default RestaurantLandingPage;
The url-param "toggleParameter" is "profile" or "menu". I'll access it with useParams(). Now if I press the button Profile the url-param "toggleParameter" should switch to Profile and if I press the button Menu the url-param "toggleParameter" should switch to Menu. I thought I could use Redirect like this:
<button
className="switchButton"
onClick={()=>{changeParameterToProfile();
<Redirect to={/restaurant/{profileToggle}/>}}
style={profileToggle==="profile"? {textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6C5AF2'}:{}}>
Profile
</button>
But this doesn't work. I'm a little bit confused with all the react-router possibilities because I haven't found the right one yet.

React doesn't allow updating the state when the component is unmounted & it will cause some serious memory leak hidden before your eyes. Also setState function is an Asynchrounus function & it can get called after the history.push method. Changing the route with history.push will unmount the component & in some cases setState might get called afterwards causing state update on unmounted component. Also the value of profileToggle will only get changed after setProfileToggle is called keeping the value of profileToggle same & history.push will also use the previous value & you or the user have to click the button twice to go to /restaurant/profile or /restaurant/menu
Code:
import {useParams,useHistory } from "react-router-dom";
function RestaurantLandingPage(){
const history = useHistory()
const {toggleParameter} = useParams();
const changeParameterToProfile =()=>{
history.push(`/restaurant/profile`)
};
const changeParameterToMenu=()=>{
history.push(`/restaurant/menu`)
}
return(
......
<button
className="switchButton"
onClick={()=>changeParameterToProfile()}
style={toggleParameter==="profile"?
{ textDecoration:'underline',
textDecorationThickness:'3px',
textDecorationColor:'#6C5AF2'
}:{}
}
>
Profile
</button>
......
)
}

Try this
import {useParams,useHistory } from "react-router-dom";
function RestaurantLandingPage(){
const history = useHistory()
const {toggleParameter} = useParams();
const changeParameterToProfile =()=>{
setProfileToggle("profile");
history.push(`/restaurant/${profileToggle} `)
};
const changeParameterToMenu=()=>{
setProfileToggle("menu")
history.push(`/restaurant/${profileToggle} `)
}
return(
......
<button
className="switchButton"
onClick={()=>{changeParameterToProfile();}
style={profileToggle==="profile"? {textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6
C5AF2'}:{}}>
Profile
</button>
<button
className="switchButton"
onClick={()=>{changeParameterToMenu();}
style={profileToggle==="profile"?
{textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6
C5AF2'}:{}}>
Menu
</button>
......
)
}
Let me know if it works

Related

How to route between pages with minimum re-render in a dashboard in Next.js

The Dashboard looks something like this:
export default function Dashboard({ children }) {
return (
<>
<DashboardLayout menu={menu}>
<DashboardPage variant="cards" flow="one-one">
{children}
</DashboardPage>
</DashboardLayout>
</>
)
}
The Dashboard has a sidebar menu on the left which allows for navigating between different DashboardPages. Because the Dashboard pages all share components like the menu, the sidebar, the footer, etc., I ideally don't want to re-render these components.
If I use the Next.JS native <Link> component, then the all components get re-rendered.
The only alternative I see to this is using a React hook like useState or useReducer to set which pages gets rendered as such:
export default function Dashboard() {
const [state, dispatch] = useReducer();
return (
<>
<DashboardLayout menu={menu}>
<DashboardPage variant="cards" flow="one-one">
{state == 'page1' && <DashboardPage1 />}
{state == 'page2' && <DashboardPage1 />}
{state == 'page3' && <DashboardPage1 />}
...
{state == 'pageN' && <DashboardPageN />}
</DashboardPage>
</DashboardLayout>
</>
)
}
Is there a way to use routing such as or the useRoute hook and avoid re-rendering certain components? For example, whenever I change between dashboard pages, I see that a console log inside the "DashboardLayout", which does not need to re-render, gets printed 4 times.
You can use the built-in tag of NextJS and NextJS Router
{/* Adding the Link Component */}
<Link href="/"> Dashboard</Link>
import { useRouter } from 'next/navigation';
export default function Page() {
const router = useRouter();
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
);
}
Or you can use React-Router-DOM for navigation using useNavigate hook
An eg. of React-router
import { useNavigate } from "react-router-dom";
const Page = () => {
const navigate = useNavigate();
return (
<button onClick={() => navigate('/pagename')}>
Go To Page
</button>
);
}

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

How to check a condition before the render of the component happen?

I am trying to redirect the user to the /dashboard page if the user is already signed in. However the home page (/) will always get rendered first and then useEffect kicks in, then the route would be redirected to /dashboard.
If you go to https://vercel.com, and if you are signed in then you don’t pause at the homepage route first and then get redirected to https://vercel.com/dashboard.
This is how I am approaching it right now (BTW I am using nextjs). I am using useLayoutEffect to check if the currentUser(comes from my useContext hook) is available, and if it is then push it to /dashboard:
import React, { useLayoutEffect } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
const Home = () => {
const router = useRouter();
const { currentUser } = useAuth();
useLayoutEffect(() => {
if (currentUser && currentUser.email) {
router.push('/dashboard');
}
}, [currentUser]);
return (
<Container maxWidth="sm" className={classes.root}>
<Grid container className={classes.gridContainer}>
<Link href="/login">
<Button>
Login
</Button>
</Link>
<Link href="/signup">
<Button>
Sign up
</Button>
</Link>
</Grid>
</Container>
);
};
export default Home;
The user has to go somewhere essentially. I suppose the currentUser is fetched in the context. While this request takes place you could expose a loading state from the context as well, and show an indication that you are fetching data.
It has no value for the user to see the /home screen when he is possibly redirected to /dashboard after 1sec, so show a spinner or something better until it is clear where the user has to go.
I would not do it with useLayoutEffect. This hook is reserved for changes regarding the DOM or rendering, not networking workarounds.
What do you think if you add a flag to delay render until you check if the user logged in?
Also, I believe you can just use useEffect here.
import React, { useEffect, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
const Home = () => {
const [checked, setChecked] = useState(false);
const router = useRouter();
const { currentUser } = useAuth();
useEffect(() => {
if (currentUser && currentUser.email) {
router.push('/dashboard');
} else {
setChecked(true)
}
}, [currentUser]);
if (!checked) return null;
return (
<Container maxWidth="sm" className={classes.root}>
<Grid container className={classes.gridContainer}>
<Link href="/login">
<Button>
Login
</Button>
</Link>
<Link href="/signup">
<Button>
Sign up
</Button>
</Link>
</Grid>
</Container>
);
};
export default Home;

How do I find out if it's possible to goBack() or goForward() using react-router?

If user has navigated to another route, he can be navigated back to the previous page.
If user has navigated back, he can be navigated forward to the previous page.
In react-native, react-nativation provides useNavigation hook so I can simply perform:
import React from 'react'
import { useNavigation} from 'react-navigation'
function Test()
{
const navigation = useNavigation()
return (
<div>
{ navigation.canGoBack() && <button onClick=(() => navigation.goBack())>Go Back</button>
</div>
)
}
in ReactJS, react-router provides useHistory hook so I can perform history.goBack() but not history.canGoBack().
import React from 'react'
import { useHistory } from 'react-router'
function Test()
{
const history = useHistory()
return (
<div>
{ history.canGoBack() && <button onClick=(() => history.goBack())>Go Back</button>
{/* history.canGoBack() is not a function */}
</div>
)
}
Does react-router provide a method to let us know if we can go back or forward?
import {History} from 'react-router';
var canGoBack = (History.length > 1);
Can you please try this one? I think this may solve the issue.

React-router custom prop not passing to component. ternary operator not working correctly

In React i have my App.js page where i keep my states. I'm importing user1.js component to App.js, and in user1.js component i have a link button that takes me to path /user2.
When i click the button, React will set state property called testValue to true and in user2.js page ternary operator should choose the first value - test works because of that. But for some reason it does not work.
Any help?
APP.JS
import React, { Component } from 'react';
import './App.css';
import User1 from './components/user1';
class App extends Component {
constructor(props){
super(props);
this.state = {
testValue:false
};
}
change = () => {
this.setState({
testValue:true
},() => {
console.log(this.state.testValue)
});
}
render() {
return (
<div className="App">
<User1 change={this.change}/>
</div>
);
}
}
export default App;
USER1.JS
import React from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import User2 from './user2.js';
const User1 = (props) => {
return(
<BrowserRouter>
<div>
<Link to ="/user2">
<button onClick={props.change}>Next page</button>
</Link>
<Switch>
<Route path="/user2" exact component={User2}/>
</Switch>
</div>
</BrowserRouter>
); // end of return
};
export default User1;
USER2.JS
import React from 'react';
const User2 = (props) => {
console.log(props)
return(
<div>
{props.testValue ?
<p>test works</p>
:
<p>test does not work</p>
}
</div>
);
};
export default User2;
This is what i expected - test works
This is what i got - test does not work
You want to pass a custom property through to a component rendered via a route. Recommended way to do that is to use the render method.
<Route path="/user2" exact render={(props) => <User2 {...props} testValue={true} />} />
I think a valid inquiry here would be what are you wanting to pass through as an extra prop? whats the use case here? You may be trying to pass data in a way you shouldn't (context would be nice :D).

Resources