HIghlight Active Link in the Navbar , React - reactjs

This is a simple version of the problem not the actual problem.
React code:
<Router>
<Navbar/>
<Routes>
<Route path="/:genreId" element={<MovieList/>} />
<Routes>
<Router>
This problem is that in the Navbar, I have several button to go the particular genre.
I want to highlight active button in the Navbar (when I click "comedy" the "comedy" button should be lit up)
some solutions
State => will not work if I reload
Session Storage => will not work if I come be website for the first time
Extract the params using the useParams Hook => this will not work since param "genreId" is available to the movieList component not the navbar
Extract from the window.location => Don't want to do it since it look too ad-hock
What is the right method?

I would suggest #3 "Extract the params using the useParams Hook => this will not work since param "genreId" is available to the movieList component not the navbar". You are correct though, this doesn't work since the Navbar component is rendered outside the Routes component and won't have access to the currently matched route. To resolve this you should move the Navbar component inside the Routes component. To make this work you'll create a layout route component that renders the Navbar component and an Outlet component for nested routes to render their element prop into.
Example:
import { Outlet } from 'react-router-dom';
const Layout = () => (
<>
<Navbar />
<Outlet />
</>
);
Then render the MovieList route as a nested route of the layout route.
<Routes>
<Route element={<Layout />}>
<Route path="/:genreId" element={<MovieList />} />
</Route>
</Routes>
The Navbar component can now safely access the genreId route path param and use it to apply any logic necessary to mark a button as "active. Here's a simple example:
const genres = ["action", "comedy", "drama"];
const Navbar = () => {
const navigate = useNavigate();
const { genreId } = useParams();
const navigateTo = (genreId) =>
navigate(generatePath("/:genreId", { genreId }));
return (
<>
{genres.map((genre) => (
<button
key={genre}
className={["genreButton", genreId === genre && "active"]
.filter(Boolean)
.join(" ")}
type="button"
onClick={() => navigateTo(genre)}
>
{genre}
</button>
))}
</>
);
};

I usually use IndexDB for that kind of stuff. To manage the IndexDB perfectly you can use localforage. You can check this link.localforage

You can use NavLink instead of Link to design the active route. Here is the documentation.
<Router>
<Navbar/>
<Routes>
<Route path="/:genreId" element={<MovieList/>} />
<Routes>
<Router>
In the Navbar component you can write like this to have the active link:
<nav>
<NavLink to="/:genreId">Genre Name</NavLink>
</nav>

Related

Route to another component and page when click on an image reactjs

Expected: when clicking on the image from one component(PhotosList), it routes to another page with the specified url to view the other component(Image details)
Reality: what happens that when clicking on the image, both components are rendered on the same page but with the different url.
using import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
Here's the return of the functional component(PhotosList)
return (
<Router>
<div className="layout">
<Masonry gutter={"5 px"} columnsCount={3}>
{photosList.map((photo: any) => (
<Link to={"/details"}>
<img src={photo.download_url} />
</Link>
))}
</Masonry>
<button onClick={loadMore} className="btn-grad">
{isLoading ? "Loading..." : "Load More"}
</button>
<Switch>
<Route exact path={"/details"} component={ImageDetails}></Route>
</Switch>
</div>
</Router>
);
and the app file is
const App = () => {
return (
<div className="App">
<header>Album</header>
<PhotosList />
</div>
);
};
export default App;
return (
<div className="layout">
<Masonry gutter={"5 px"} columnsCount={3}>
{photosList.map((photo: any) => (
<Link to={"/details"}>
<img src={photo.download_url} />
</Link>
))}
</Masonry>
<button onClick={loadMore} className="btn-grad">
{isLoading ? "Loading..." : "Load More"}
</button>
</div>
);
and in the app file
const App = () => {
return (
<Router>
<div className="App">
<header>Album</header>
<PhotosList />
<Switch>
<Route exact path={"/photilist"} component={PhotosList}></Route>
<Route exact path={"/details"} component={ImageDetails}></Route>
</Switch>
</div>
</Router>
);
};
export default App;
From what I can gather, you have rendered the PhotoList component which then renders a list of photos each with a link to /details.
In the same component you set up the /details route.
Whenever you click the Link which redirects to /details, the PhotoList component will (should) unmount. Once this component is unmounted, so is the Route which sets up the component which should be rendered on /details route. So you are on /details but the router has no idea what component should be rendered because the following code:
<Switch>
<Route exact path={"/details"} component={ImageDetails}></Route>
</Switch>
no longer exists.
To avoid this, don't put Routes this far down in your application. Routes should always be close to the top level (children/grandchildren of Router). This makes projects way easier to manage. I do have projects with upwards of 100 routes and I do split up my routes into seperate files (for example each module will have a ModuleRoutes.js file in which I set up the routes for that module), then you can create a RootRouter components which renders these seperate JS files (Which are really React components) and you render the RootRouter inside of your <BrowserRouter> / <Router>.
You can abstract this even higher by creating an array of objects which hold details about each route (route name, path, component for the route, function / data you want to pass down to the component, access control (true/false) defined as a logical expression of your choosing - i.e. user.isLoggedIn === true). By employing this abstraction you minimize the room for error because errors and bugs are much easier to spot when they are in this kind of form (object) as opposed to JSx which takes up more lines of code, has messy indentation and is not very clean in general.

React Router Render props is not rendering component when using params

this is my first post in Stackoverflow and I hope you guys can give me some guidance.
I'm currently learning React Router and it seems that I got stucked on an error or bug that I can't find to solve. To give you a little overview about the setup, here it is:
I'm using CRA with React-Router-DOM 5.0.1.
I'm using React Hooks to practice and using latest version of React 16.8.6
This is an app that reads a local API and it renders its data on different components. Here is the code:
App.js
import React, { Fragment, useState, useEffect } from 'react';
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
import axios from 'axios';
import Writers from './Writers';
function App() {
const [writers, setWriters] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios('http://localhost:3004/writers');
setWriters(result.data);
};
fetchData();
}, []);
return (
<BrowserRouter>
<Fragment>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/writers'>Writers</Link>
</li>
</ul>
<Switch>
<Route exact path='/' render={() => <div>Home</div>} />
<Route
exact
path='/writers'
render={props => <Writers {...props} writers={writers} />}
/>
</Switch>
</Fragment>
</BrowserRouter>
);
}
export default App;
Writers.js
import React, { Fragment } from 'react';
import { Route, Link } from 'react-router-dom';
import Writer from './Writer/index';
const Writers = props => {
console.log(props);
return (
<Fragment>
<ul>
{props.writers.map(({ id, name }) => (
<li key={id}>
<Link to={`${props.match.url}/${id}`}>{name}</Link>
</li>
))}
</ul>
<Route
path={`${props.match.url}/:writerID`}
render={props => <Writer {...props} />}
/>
</Fragment>
);
};
export default Writers;
Writer.js
import React, { Fragment } from 'react';
const Writer = ({ match, id, name, description }) => {
console.log(match);
return <Fragment>id name description</Fragment>;
};
export default Writer;
So I'm having an issue in Writers.js where I'm passing the params "/:writerID", this params doesn't get to Writer component and actually Writer component never gets rendered at all
<Route
path={`${props.match.url}/:writerID`}
render={props => <Writer {...props} />}
/>
If I remove :writerID from the path prop, the component Writer gets rendered BUT it doesn't allow me route it the unique path that I'm looking for.
Any idea why this might be happening? Any help will be greatly appreciated.
I have tried making sure I have the correct exports on my files and they are, in fact, I don't see any errors from the CRA logs.
Also, I remove the params from the path props and it seems that the Writer components renders but if I put it again, it doesn't work.
App.js passing Writers with props and writers data
<Route
exact
path='/writers'
render={props => <Writers {...props} writers={writers} />}
/>
Writers.js receives the data and Route props so I can access Match props but this component does NOT render Writer component neither get the match.params prop.
<Route
path={`${props.match.url}/:writerID`}
render={props => <Writer {...props} />}
/>
Expected behavior: Writer.js should be render when trying to click on any individual writer and it should allow me to get the match.params prop.
Actual Behavior: Writer.js does not gets rendered and if I remove the path prop from Route, it gets render but I can't access the individual id.
In your Route path for /writers you are using the exact prop, which will lead to any nested Route not being rendered. Remove the exact prop and your Routes will work
<Route
exact
path='/writers'
render={props => <Writers {...props} writers={writers} />}
/>
Also as a thumb rule you should use match.url for Link path and match.path for a Route path

Link to not rendering the component again

I am using React Link to="" property. I have a url like below.
http://localhost:3000/work/109
After clicking on link, It is successfully going to url like below, But not re-rendering the component again.
http://localhost:3000/work/107
Below is my file, where i am using react-router
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const RouteWithSubRoutes = route => (
<React.Fragment>
<Route exact path="/" render={()=>(<Redirect to="/home" />)}/>
<Route exact path={route.path} render={props => (
<route.component {...props} routes={route.routes} onOpenNav={route.onOpenNav}/>
)} />
</React.Fragment>
);
Is there any other property of React, which i am not using.
Note: I am going to same url, But with diffrence id.
you should use 'react-router' params in your Route component path.
also this way new params would send to your component after clicking on Link component.
you could check this.props.match.params to sure it gets update.
then since you want component to re-render you should use getDerivedStateFromProps to get new value and set it to your state.

Single application appbar to show back button when navigating throughout the application

I have my app.js file which looks like this:
class App extends Component {
render() {
const {classes} = this.props;
return (
<React.Fragment>
<AppBar/>
<BrowserRouter>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={100}
classNames="someanimation"
>
<Switch location={location}>
<Route exact path="/" component={HomePage} />
<Route exact path="/contact" component={ContactPage} />
<Route exact path="/customer/:id" component={CustomerPage} />
<Route component={ErrorPage} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</BrowserRouter>
</React.Fragment>
);
}
}
This component has an appbar and a router with routes underneath. The point of the appbar being here is that the application then only has one appbar at all times throughout the application. Only the pages underneath change.
Inside my contact page I have a button that goes to a custom page with a param passed:
<Button component={Link} to={'/customer/' + customerID[99]}>
When the application goes to this customer page I want the appbar to show a back button. So I have to somehow notify the appbar to show this button and then also know which page to go back to (it should be the last page). I have googled some examples but I can't find one that fits this case.
React router contains withRouter higher-order component that can provide relevant props to application bar component.
React router is powered by history.js library which is an abstraction over browser history API. While it can be used to navigate over browser history with history.goBack(), it won't work well with navigating over application alone because window history may contain other websites.
The component can look like this (a demo) and should be a child of router component to get router props:
#withRouter
class AppBar extends Component {
state = {
locations: [this.props.location]
};
componentDidMount() {
this.props.history.listen((location, action) => {
if (action === 'REPLACE')
return;
this.setState({
locations: [location, ...this.state.locations]
})
});
}
back = () => {
const [location, ...locations] = this.state.locations;
this.setState({ locations });
this.props.history.replace(location);
}
render() {
return (
<>
{this.state.locations.length > 1 && <button onClick={this.back}>Back</button>}
</>
);
}
}
It keeps track on location changes and navigates through them. It would be more complex task to keep it in sync with browser history navigation buttons (back and forward).

React Router v4 - Redirect to home on page reload inside application

I need to redirect to home page when user refreshes other pages inside my application. I am using React router v4 and redux. Since the store is lost on reload, the page user reloaded is now empty and hence I want to take him back to a page that does not need any previous stored data. I don't want to retain state in localStorage.
I tried to handle this in event onload but it did not work:
window.onload = function() {
window.location.path = '/aaaa/' + getCurrentConfig();
};
You can try creating a new route component, say RefreshRoute and check for any state data you need. If the data is available then render the component else redirect to home route.
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
const RefreshRoute = ({ component: Component, isDataAvailable, ...rest }) => (
<Route
{...rest}
render={props =>
isDataAvailable ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/home"
}}
/>
)
}
/>
);
const mapStateToProps = state => ({
isDataAvailable: state.reducer.isDataAvailable
});
export default connect(mapStateToProps)(RefreshRoute);
Now use this RefreshRoute in your BrowserRouter as like normal Route.
<BrowserRouter>
<Switch>
<Route exact path="/home" component={Home} />
<RefreshRoute exact path="dashboard" component={Dashboard} />
<RefreshRoute exact path="/profile" component={ProfileComponent} />
</Switch>
</BrowserRouter>
It is so amazing that you don't want to keep state of user route map in browser but you use react-router!, the main solution for your case is do not use react-router.
If you don't use it, after each refresh the app come back to main view of app, If you wanna see route map in address bar without any reaction use JavaScript history pushState.
Hope it helps you.

Resources