React & Material UI - How to Remove Nav Buttons/Links based on Routes? - javascript

The code below is the main app.js entry of a React app and it includes the routers endpoints.
Below the app.js code is the code of a Nav (navigation) component. I want to know how to structure this code so that when a user goes to a specific route the respective link in the Nav is removed.
In other words if the user is at localhost:3000/calendar the tab with the word "calendar" should not appear in the Nav component.
I could parse the endpoints and do this with a bunch of ugly conditionals that render different Nav code based on the parsed endpoints - but I figure there is a simpler way that I don't see to do what I want.
Thanks.
App.js
function App(){
...code
function redirectToClientsList(){
window.location.href = "/";
}
function redirectToCalendar(){
window.location.href = "/calendar";
}
return (
<div className="App">
<MuiPickersUtilsProvider utils={MomentUtils}>
<Nav redirectToClientsList = {redirectToClientsList} redirectToCalendar={redirectToCalendar} />
<BrowserRouter>
<div>
<Switch>
<Route exact={true} path="/" component={Landing} />
<Route exact={true} path="/test" component={Test} />
<Route exact={true} path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact={true} path="/calendar" component={Calendar} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</div>
);
}
export default App;
Nav component.
function Navbar(props){
const {classes} = props;
return (
<AppBar className={classes.bgColor} >
<Toolbar>
<Button color="inherit" onClick ={props.redirectToClientsList}>Clients</Button>
<Button color="inherit" onClick ={props.redirectToCalendar}>Calendar</Button>
<Button className={classes.saveDataButton} style={{float:"right"}} color="inherit">SAVE</Button>
</Toolbar>
</AppBar>
)
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
const styles = theme => (navbarStyle(theme));
export default withStyles(styles)(Navbar);

You can use the withRouter provided by the react-router-4 package.
Here's an example using the withRouter
You can get access to the history object’s properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
import React, { Component } from "react";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends Component {
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" to the router
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
After getting access to the current location aka route object you can do a simple if check to determine which buttons should be displayed on that particular route. I would create a helper function that should determine just that, put it inside of the componentDidMount lifecycle or the equivalent useEffect hook (if you choose that approach)`, save the result to the state and finally do an if check and depending on its results, show/ hide buttons
Important Note:
withRouter does not subscribe to location changes like React-Redux’s connect does for state changes. Instead, re-renders after location changes propagate out from the <Router> component. This means that withRouter does not re-render on route transitions unless its parent component re-renders.
You could wrap your app.js component with the withRouter HOC and get the necessary props in your Nav.js component
// ....
<div className="App">
<MuiPickersUtilsProvider utils={MomentUtils}>
<BrowserRouter>
<div>
<Nav redirectToClientsList = {redirectToClientsList} redirectToCalendar={redirectToCalendar} />
<Switch>
<Route exact path="/" component={Landing} />
<Route exact path="/test" component={Test} />
<Route exact path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact path="/calendar" component={Calendar} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</div>
// LET'S NOW WRAP OUR App.js COMPONENT WITH THE withRouter HOC - [don't forget to import it first] :)
export default withRouter(App);
Nav.js
function Navbar(props){
const {classes} = props;
// We're interested in the current path,
// so, we'll destructure it from the location property
const { pathname } = props.location;
return (
<AppBar className={classes.bgColor} >
<Toolbar>
// Here we're going to do our check
{pathname !== "calendar" ?
<Button color="inherit" onClick ={props.redirectToCalendar}>Calendar</Button> : null}
<Button color="inherit" onClick ={props.redirectToClientsList}>Clients</Button>
<Button className={classes.saveDataButton} style={{float:"right"}} color="inherit">SAVE</Button>
</Toolbar>
</AppBar>

Related

Rendering child components without the parent component in React Router

I have simple react app that lists different shops from a mongodb database for example (HM, Amazon, Lindex etc) and displayed as cards with links (As seen in Boutique.js)
Desired solution:
I want so that each pressed card leads to a new page using router.
For example: If i press the card that is named "HM" then I want to be directed to /boutiques/HM that runs the Template component (as shown in Boutique.js) without parent component being rendered (except for the navbar)
The next card that is named "Amazon" should direct me to /boutiques/Amazon
The next card etc etc
Current solution:
My current solution renders the Template component under the cards whenever you click any of the cards. The cards are still visible in the page. I want so that the Template component renders without the parent showing (the cards). It should be just the navbar and a blank whitepage
Question:
How do I restructure my current solution to reach my desired solution?
App.js
import { BrowserRouter, Link, Route, Routes } from "react-router-dom";
import React from "react";
import "./CouponDeals.css";
import Boutiques from "./Boutiques";
import Home from './Home';
function App() {
return (
<BrowserRouter>
<div className="main-container">
<div className="navbar-section-container">
<div class="navbar">
<Link to="/">Hem</Link>
<Link to="/boutiques">Butiker</Link>
</div>
</div>
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/boutiques/*" element={<Boutiques />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
Boutiques.js
import React, { useEffect, useState } from "react";
import {Link, Route, Routes } from "react-router-dom";
import axios from "./Axios";
import Template from "./Template.js";
const Boutiques = () => {
const [deals, setDeals] = useState([]);
useEffect(() => {
async function fetchData() {
const req = await axios.get("/butiker");
setDeals(req.data);
}
fetchData();
}, []);
return (
<React.Fragment>
<div className="header-section">
<h1 className="title">All Boutiques</h1>
</div>
<div className="card-section-container">
<div className="deals-container">
{deals.length > 0 ? (
deals.map((deal, index) => (
<div key={index} className="deal">
<div className="button-container">
<Link to={`${deal.name}`} className="butiker-button">
<img src={deal.imgUrl} alt={deal.name} />
</Link>
</div>
</div>
))
) : (
<p className="no-results">No results found</p>
)}
</div>
</div>
<Routes>
{deals.map((deal, index) => (
<Route
key={index}
path={`${deal.name}`}
element={<Template name={deal.name} />}
/>
))}
</Routes>
</React.Fragment>
);
};
export default Boutiques;
The selected boutique is showing below the cards because you implemented a router in the bottom Boutiques.js. Instead of using this approach you should try to add another dynamic Route in App.js that uses the name of the boutique as a parameter.
App.js
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/boutiques/" element={<Boutiques />} />
<Route path="/boutiques/:name" element={<Template />} />
</Routes>
You will probably have to refactor your Template component so it retrieves the id of the boutique from the URL. You can do this like so:
let { name } = useParams();

React Router v5: Route defined in child component not working

My application is using react-router-dom v5.3 and I'm having trouble routing from the root url of my application to a child component (called the "See All" Page) while also passing props down. Currently, my code just renders an empty page whenever I navigate to the child component.
RootRouter.js:
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route
path="/"
exact
render={() => <HomeView />}
/>
</Switch>
</BrowserRouter>
);
}
Homeview.js:
function HomeView() {
const seeAllViewTitle = "some_title_here"
return (
<div>
<div>Some content here!</div>
<Link to={`/seeall/${seeAllViewTitle}`}}>
<Button/>
</Link>
<Route path={`/seeall/${seeAllViewTitle}`}>
<SeeAllView
groupTitle={""}
pageData={[]}
eventHandler={some_function_here}
/>
</Route>
</div>
);
}
If I were to put the Route that is currently in homeview.js inside of Rootrouter.js, the component shows up, but I can't pass any props into it from there.
Issue
The HomeView component is rendered only when the path is exactly "/". When the link is clicked and navigates to "/seeall/some_title_here " the path no longer exactly matches and the HomeView component unmounts.
Solution
Remove the exact prop from the root route so nested/sub routes can also be matched and rendered.
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={HomeView} />
</Switch>
</BrowserRouter>
);
}
If you did not intend for these components to be rendered at the same time then move the nested route out to the RootRouter component.
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route path="/seeall/:title">
<SeeAllView
groupTitle={""}
pageData={[]}
eventHandler={some_function_here}
/>
</Route>
<Route path="/" component={HomeView} />
</Switch>
</BrowserRouter>
);
}
...
function HomeView() {
const seeAllViewTitle = "some_title_here"
return (
<div>
<div>Some content here!</div>
<Link to={`/seeall/${seeAllViewTitle}`}}>
<Button/>
</Link>
</div>
);
}
Are you remembering to receive props in the function declaration for HomeView? usually, you'll need to explicitly define that you are receiving props, either with a props variable or by defining specific prop names in an object syntax

Trigger a rerender of parent component when a child component is rendered

I am using the following material-ui theme Paperbase and within the Header.js component, I have the following useEffect hook:
const [temperature, setTemperature] = useState([]);
const getTemperature= async () => {
try {
const response = await fetch('/get-temperature')
const tempData = await response.json();
setTemperature(tempData);
} catch (err) {
console.error(err.message);
}
};
useEffect(() => {
getTemperature();
}, []);
The main purpose of this, is to display the current temperature as info, within the header component, which gets displayed at first page load/render.
Now within my App.js below, I have the following return setup where the above Header component is called.
return (
<Router>
<UserProvider myinfo={myinfo}>
<Switch>
<Route path="/">
<ThemeProvider theme={theme}>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer}>
<Hidden xsDown implementation="css">
<Navigator />
</Hidden>
</nav>
<div className={classes.app}>
<Header
onDrawerToggle={handleDrawerToggle}
/>
<main className={classes.main}>
<Switch>
<Route exact path="/new-user"
render={(props) => <Content key={props.location.key} />}
/>
<Route exact path="/view-results"
render={(props) => <ViewResults key={props.location.key} />}
/>
</Switch>
</main>
</div>
</div>
</ThemeProvider>
</Route>
</Switch>
</UserProvider>
</Router>
);
My question is, how can I trigger a rerender of Header (parent) whenever the user routes to either /new-user or /view-results which in turn calls either Content.js or ViewResults.js, inorder to make the useEffect in Header.js refresh the data, from the REST api fetch and display the latest temperature in the header again?
Ideally anytime Content.js or ViewResults.js is rendered, ensure that Header.js getTemperature() is called.
Any help would be much appreciated.
Your current code is pretty close to a multi layout system. As being a component child of Route, you can access the current location via useLocation() or even the native window.location.pathname.
This is my example of multi layout React app. You can try to use it to adapt to your code.
The MainLayout use a fallback route when no path is specified. It also contains a Header and include a page
const Dispatcher = () => {
const history = useHistory();
history.push('/home');
return null;
};
const App = () => (
<BrowserRouter>
<Switch>
<Route
component={Dispatcher}
exact
path="/"
/>
<Route
exact
path="/login/:path?"
>
<LoginLayout>
<Switch>
<Route
component={LoginPage}
path="/login"
/>
</Switch>
</LoginLayout>
</Route>
<Route>
<MainLayout>
<Switch>
<Route
component={HomePage}
path="/home"
/>
</Switch>
</MainLayout>
</Route>
</Switch>
</BrowserRouter>
);
And here is the code for MainLayout
const MainLayout = ({ children }) => (
<Container
disableGutters
maxWidth={false}
>
<Header location={props.location} />
<Container
component="main"
maxWidth={false}
sx={styles.main}
>
{children}
</Container>
<Footer />
</Container>
);
Now that Header can be anything. You need to put a capture in this component
import { useLocation } from 'react-router-dom'
cont Header = (props) => {
const { pathname } = useLocation();
//alternatively you can access props.location
useEffect(() => {
if (pathname === '/new-user') {
getTemperature();
}
}, [pathname]);
};
Note that Header is not a direct descendant of Route therefore it cannot access the location directly via props. You need to transfer in chain
Route -> MainLayout -> Header
Or better use useLocation

react router use params returns empty object

I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.

Controlling redux properties in navigation bar in reactjs

I have a component (DatasetPage) which renders some images of different datasets for an image selected. This depends on the tab clicked on the navigation top bar. The thing is that one of the dataset in my case is very big and so it takes more time to load the page. If I wait until the page is loaded everything works well but, if I click into another tab (another dataset) before the reducer delivers the properties to my component (ImageWithRelateds), the new page is loaded with the information of the other(last) dataset, which was not loaded yet.
So, I have thought about a solution which could be block the navigation through the navigation bar while I have the Loading running. But the thing is that this loading thing is controlled in the ImageWithRelateds.js component and the navigation bar is controlled from App.js. So I would need to access from App.js to the isLoading attribute of ImageWithRelateds.js (which I already have) but I don't know how to do it. I just found ways to access from children to parent attributes but not backwards. If you could help me with that or just proposing another solution I would be very grateful.
App.js
import React, { Component } from 'react';
import { IndexLink } from 'react-router';
import '../styles/app.scss';
class App extends Component {
constructor(props){
super(props);
this.options = this.props.route.data;
}
renderContent(){
if(this.props.route.options) {
return(<div className="navbar-header nav">
<a className="navbar-brand" id="title" href="/" >
IMAGES TEST WEB
</a>
<li className="nav-item" key={`key-9999`}>
<IndexLink to='/home' className="nav-link active" href="#">HOME</IndexLink>
</li>
{this.props.route.options.map((opt,i)=>{
return this.returnOptions(opt,i);
})}
</div>
);
}
}
returnOptions(opt,i){
return(<li className="nav-item" key={`key-${i}`}>
<IndexLink to={opt.link} className="nav-link active"
href="#">{opt.name}</IndexLink>
</li>);
}
render() {
return (
<div className="main-app-page">
<nav className="navbar navbar-default color-navbar fixed">
<div className="container-fluid">
{this.renderContent()}
</div>
</nav>
<div className="content">
{this.props.children}
</div>
</div>
);
}
}
export default App;
Routes.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, IndexRoute, browserHistory, Link } from 'react-router';
import App from './common/App';
import NotFound from './common/NotFound';
import Store from './store';
import Home from './components/Home';
import DatasetPage from './components/Images/DatasetPage';
import ImageWithRelateds from './components/Images/ImageWithRelateds';
import { options_NavBar } from './customize.js';
import { getQimList, resetQimList} from './actions/index';
const Test = ()=>{
return(<h2 style={{"paddingLeft":"35%"}} >W E L C O M E !</h2>)
};
export default (
<Route path="/" components={App} options={options_NavBar} history={browserHistory}>
<IndexRoute components={Test}/>
<Route path="/home" component={Home} />
<Route path="images" >
<Route path="oxford" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('oxford'));
}} />
<Route path="paris" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('paris'));
}} />
<Route path="instre" component={DatasetPage} onEnter={(e)=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('instre'));
}} />
<Route path=":id" component={ImageWithRelateds} />
</Route>
<Route path="*" component={NotFound} />
</Route>
);
Thank you so much!
One of the most basic principle in react that the parent give props to children, and the children emit events to the father (to avoid 2 way binding)
so, your App.js should have state, with isLoading variable, and to the ImageWithRelateds component you should pass an event (function) something like this:
<Route path=":id" render={(props) => <ImageWithRelateds {...props} onFinishLoading={loadingFinished} />}>
and inside your component (that should be with state) should have function like this:
function loadingFinished() {
this.setState(prev => ({ ...prev, isLoading: false }))
}
and then, you would know inside you App.js if the loading inside the ImageWithRelateds component finished, and then you would able to do any validation you would like
I suggest to you to read this article about passing events (functions) to components, why it's needed and how to do it effectively
Hope that helped!
Edit:
your final Routes.js code should look something like that:
export default class Routes extends React.Component {
constructor() {
super();
this.state = { isLoading: false };
this.onLoadingFinishded = this.onLoadingFinishded.bind(this);
}
onLoadingFinishded() {
this.setState(state => {
...state,
isLoading: false
});
}
render() {
return <Route path="/" components={App} options={options_NavBar} history={browserHistory}>
<IndexRoute components={Test}/>
<Route path="/home" component={Home} />
<Route path="images" >
<Route path="oxford" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('oxford'));
}} />
<Route path="paris" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('paris'));
}} />
<Route path="instre" component={DatasetPage} onEnter={(e)=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('instre'));
}} />
<Route path=":id" render={(props) => <ImageWithRelateds
{...props}
onLoadingFinished={this.onLoadingFinishded} />} />
</Route>
<Route path="*" component={NotFound} />
</Route>
}
}
(i can't ensure that code exactly running because i don't have all of your project, but that most likely it)

Categories

Resources