React Router active Class for home page - javascript

I've got a navigation that I am having a hard time trying to style. I have been able to add active classes to the navigational elements and apply styles to them, however the main dashboard which functions as a homepage does not get the dedicated active class applied to it when I click it. After doing a little bit of digging around in the app, I believe the AppRouter.js or routeConfig.js might be the issue.
The navigational elements are displayed with the Navlink code below:
renderNavigationItem = (navigationItem) => {
const { classes } = this.props;
const { id, name, parentUrlLink } = navigationItem;
return (
<NavLink exact key={id.toString()} to={{ pathname: parentUrlLink }} className="desk-link"
activeClassName='is-active'>
<MenuItem className={classes.fontBoldHeader}>{name}</MenuItem>
</NavLink>
);
}
The app router looks like this:
class AppRouter extends Component {
constructor(props, context) {
super(props);
this.context = context;
}
render() {
const { account } = this.context;
return (
<Switch>
{account ? <Redirect exact from="/" to={{ pathname: '/dashboard' }} /> : <Route exact path="/" component={Home} />}
{routes.map((item) => <PrivateRoute {...item} key={`${item.path}-route`} />)}
<Route path="/sessiontimeout" render={(props) => <TimedOutPage {...props} />} />
<Route path="/500" render={(props) => <Error errorCode="500" {...props} />} />
<Route path="/503" render={(props) => <Error errorCode="503" {...props} />} />
<Route path="*" render={(props) => <Error errorCode="404" {...props} />} />
</Switch>
);
}
}
It looks like there's a specific rule that is in place for the "dashboard" page. It is also the page people go to if they click the logo so I feel like there is a specific rule that is preventing the dashboard page to inherit the "is-active" class. I am new to React so this is something that is a little over my head at the moment and any help would be greatly appreciated!
Edit: Here is where rednerNavigation is called:
renderNavigation = () => {
const { menuNavigation } = this.props;
return menuNavigation.map((item) => {
if (item.children.length > 0) {
return this.renderNavigationItemWithSubNav(item);
}
return this.renderNavigationItem(item);
});
}

Related

Protected route not working correctly with React and Firebase

I'm building a small app with firebase and react and currently working on implementing the authentication. I've set the onAuthStateChanged in my app component as a side effect and whenever user is logged in it should be redirected to a desired component from ProtectedRoute.
This works correctly but unfortunately when refreshing the page the ProtectedRoute is not rendering correct component and is just firing redirection.
I get what is happening: on refresh user is empty and only after then it change so I would expect to see a screen flicker and a proper redirection.
Could you please look at below code and maybe tell me how to fix this behavior?
App component:
const App = () => {
const [authUser, setAuthUser] = useState<firebase.User | null>(null);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
authUser ? setAuthUser(authUser) : setAuthUser(null);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute exact path={ROUTES.HOME} component={Home} />
<ProtectedRoute exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</div>
</Router>
</AuthUserContext.Provider>
);
};
Protected Route:
interface Props extends RouteProps {
component?: any;
children?: any;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
...rest
}) => {
const authUser = useContext(AuthUserContext);
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};
Found the fix. Had to add the flag checking for user authentication status (default value of that flag is set to true). Flag needs to be passed to ProtectedRoute as prop and if is True then render some loading component:
App component:
const App = () => {
const [authUser, setAuthUser] = useState(false);
const [authPending, setAuthPending] = useState(true);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
setAuthUser(!!authUser);
setAuthPending(false);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Switch>
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.HOME}
component={Home}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.ACCOUNT}
component={Account}
/>
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</Switch>
</div>
</Router>
</AuthUserContext.Provider>
);
};
ProtectedRoute:
interface Props extends RouteProps {
component?: any;
children?: any;
pendingAuth: boolean;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
pendingAuth,
...rest
}) => {
const authUser = useContext(AuthUserContext);
if (pendingAuth) {
return <div>Authenticating</div>;
}
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};

How to detect matched route from a component outside of the <Route/> component that was matched using react-router?

I've got the following structure in my React app, using react-router-dom.
<Router>
<Header/>
<Main>
<AllRoutes> // THIS HANDLES THE SWITCH WITH ALL THE ROUTES
<Switch>
<Route exact path={ROUTES.HOME} component={Home}/>
<Route exact path={ROUTES.ABOUT} component={About}/>
<Route exact path={ROUTES.PRIVACY} component={Privacy}/>
// ETC
</Switch>
</AllRoutes>
</Main>
<Footer/> // <==== FOOTER NEEDS TO KNOW WHICH ROUTE HAS BEEN MATCH
<Router>
QUESTION
Footer needs to know what <Route/> has been match. What is the best pattern to achieve that?
OPTION #1
I found the useRouteMatch hook over on react router docs:
This would kind of work, but I don't think it is good enough for my situation. Because a URL string can match a route and still don't be a valid route at the same time.
For example:
Route: /:language/privacy
Valid route: /en/privacy
Not valid route that would also match: /notALanguage/privacy
Once a route has match, I usually need to check if it is valid before rendering a component page or the 404 page.
Like:
<Route exact path={"/:language/privacy"} render={(routeProps) => {
const possibleLanguage = routeProps.match.params.language;
if (possibleLanguage in LANGUAGES) {
return(
<PrivacyPage lang={possibleLanguage}/>
);
}
else {
return(
<Page404/>
);
}
}}/>
OPTION #2
What I'm thinking about doing is:
App.js calls useLocation. So it always re-render when there is a route change.
I could add a detectRoute function in App.js to do all the route checking beforehand.
And my AllRoutes component wouldn't need a component. I would implement a native JS switch and render the corresponding route.
This way I know upfront which <Route/> is going to match and I can pass it on to <Footer/> or any component that lives outside of the matched <Route/>.
Something like this:
SandBox Link
export default function App() {
console.log("Rendering App...");
const location = useLocation();
// THIS WOULD BE THE detectRoute FUNCTION
// I COULD EVEN USE THE useRouteMatch HOOK IN HERE
const matchedRoute =
location.pathname === ROUTE1
? "ROUTE1"
: location.pathname === ROUTE2
? "ROUTE2"
: "404";
return (
<div>
<div className="App">
<Link to={ROUTE1}>Route 1</Link>
<Link to={ROUTE2}>Route 2</Link>
<Link to={"/whatever"}>Route 404</Link>
</div>
<div>
<AllRoutes matchedRoute={matchedRoute} />
</div>
</div>
);
}
function AllRoutes(props) {
switch (props.matchedRoute) {
case "ROUTE1":
return <Route exact path={ROUTE1} component={Page1} />;
case "ROUTE2":
return <Route exact path={ROUTE2} component={Page2} />;
default:
return <Route exact path={"*"} component={Page404} />;
}
}
It works. But I would like to know if there's a proper way of doing this, 'cause this seems a bit weird and there might be something out there that was specifically designed for this.
Generally you want to either:
Wrap the components together
Create another switch to route them (and pass match params)
I put together a somewhat comprehensive example of the options. Hope that helps!
import React from "react";
import "./styles.css";
import { Switch, Link, Route, BrowserRouter } from "react-router-dom";
const hoc = (Component, value) => () => (
<>
<main>
<Component />
</main>
<Footer value={value} />
</>
);
const Wrapper = ({ component: Component, value }) => (
<>
<main>
<Component />
</main>
<Footer value={value} />
</>
);
const WrapperRoute = ({ component, value, ...other }) => (
<Route
{...other}
render={props => <Wrapper component={component} value={value} {...props} />}
/>
);
const Footer = ({ value }) => <footer>Footer! {value}</footer>;
const Header = () => <header>Header!</header>;
const Another = () => <Link to="/onemore">One More!</Link>;
const Home = () => <Link to="/other">Other!</Link>;
const OneMore = () => <Link to="/">Home!</Link>;
const Other = () => <Link to="/another">Another!</Link>;
export default () => (
<BrowserRouter>
<Header />
<Switch>
{/* You could inline it! */}
<Route
path="/another"
render={() => (
<>
<main>
<Another />
</main>
<Footer value="" />
</>
)}
/>
{/* You could use a custom route component (that uses an HOC or a wrapper) */}
<WrapperRoute
component={OneMore}
path="/onemore"
value="I got one more!"
/>
{/* You could use a Higher-Order Component! */}
<Route path="/other" component={hoc(Other, "I got other!")} />
{/* You could use a wrapper component! */}
<Route
path="/"
render={() => <Wrapper component={Home} value="I got home!" />}
/>
</Switch>
{/* You could have another switch for your footer (inline or within the component) */}
<Switch>
<Route
path="/another"
render={() => <Footer value="Switch footer another!" />}
/>
<Route
path="/other"
render={() => <Footer value="Switch footer other!" />}
/>
<Route
path="/onemore"
render={() => <Footer value="Switch footer onemore!" />}
/>
<Route path="/" render={() => <Footer value="Switch footer home!" />} />
</Switch>
</BrowserRouter>
);
Note the WrapperRoute would allow you to do validation on your match params before passing them through. You could do a Redirect if needed.
What I've ended up doing:
Since I'm using Redux, I added a piece of global state to keep track of the matched route.
And I dispatch actions to update that state from the render prop from the <Route/>'s component.
<Switch>
<Route key={index} exact path={"/some-route"} render={(routeProps) => {
// HERE I DISPATCH AN ACTION TO CHANGE THE STATE FOR THE CURRENT ROUTE
dispatch({
type: UPDATE_CURRENT_ROUTE,
payload: { name: "SOME_ROUTE_NAME" }
});
return (
<PrivacyPage
{...routeProps}
/>
);
}}/>
</Switch>
And now I can do on Footer.js:
function Footer() {
const currentRoute = useSelector((state) => state.currentRoute);
// RENDER FOOTER ACCORDINGLY TO THE CURRENT ROUTE
}

React Router path var with other paths as well, React cant tell the difference

I am setting up react router for different link in my project but the problem is I need react router to tell the difference between a user username variable and other paths.
For example:
baseUrl/admin
baseUrl/daniel
React doesnt know the difference. I will have a list of usernames in a db and would return an error if the user doesnt exist then that means the page does not exist.
This is my code:
class App extends Component{
render(){
return (
<Router>
<Route exact path="/" render={props => (
<React.Fragment>
<h1>Hey</h1>
</React.Fragment>
)}
/>
<Route exact path="/admin" render={props => (
<React.Fragment>
<h1>admin</h1>
</React.Fragment>
)}
/>
<Route path="/:user" component={UserComponent}
/>
</Router>
);
}
}
You can use the match.url property to choose which component render, for example:
<Route path="/:user" render={props => {
if(props.match.url === '/admin') {
return (
<React.Fragment>
<h1>Hey</h1>
</React.Fragment>
)
} else return (<UserComponent {...props} />)
}} />

Adding Redirects programmatically to React Router

I'm trying to add a bunch of redirects given an array of old routes and new routes so I came out with this component:
const redirectUrls = [
{ oldUrl: '/robin', newUrl: '/users' },
{ oldUrl: '/batman', newUrl: '/courses' }
];
export default Redirects = () => (
redirectUrls.map((url, index) => <Route key={index} exact path={url.oldUrl} render={() => <Redirect to={url.newUrl} />} />)
);
Which works fine, but I wanted it to be simplier so I removed the Route and left only the redirect like this:
export default Redirects = () => (
redirectUrls.map((url, index) => <Redirect key={index} exact from={url.oldUrl} to={url.newUrl} />))
But it won't work, all the redirects take me to the last route, which in this case is /courses, and its weird because if I do this:
<Switch>
...
<Route path='/users' exact component={users} />
<Route path="/courses" exact component={CoursesList} />
<Redirect from='/robin' to='/users' />
<Redirect from='/batman' to='/courses' /> // having them like this works fine
</Switch>
So it makes no sense for them fail when I create them with a map and I haven't found anything that leads to the cause nor to a solution.
You need to wrap the exported redirects in a switch:
const Redirects = () => {
return (
<Switch>
{redirectUrls.map(url => (
<Redirect from={url.oldUrl} to={url.newUrl} />
))}
</Switch>
);
};
export default Redirects;
That's only if you actually need to make a component out of them. It's not totally clear what you need and are looking for since export default Redirects = () => is invalid syntax. If you just want to bundle them as some variable you can do:
export const Redirects = redirectUrls.map(url => <Redirect from={url.oldUrl} to {url.newUrl} />);
// App.js
<Switch>
<Route path='/users' exact component={users} />
<Route path="/courses" exact component={CoursesList} />
...
{Redirects}
</Switch>
Did you try this with exact props
<Switch>
...
<Route path='/users' exact component={users} />
<Route path="/courses" exact component={CoursesList} />
<Redirect exact from='/robin' to='/users' />
<Redirect exact from='/batman' to='/courses' /> // having them like this works fine
</Switch>
Reference https://github.com/ReactTraining/react-router/issues/4837

Routes inside component previously routed

I am using react-router-dom 4.2. I have my App.js with Authenticated components inside. This components are created by me and add a little of business logic, create the component via React.createElement, and route them via Route component. Nothing unusual.
The App.js:
// ...
const App = props => (
<BrowserRouter>
<Fragment>
<Switch location={location}>
<Route
exact
path={URLS.ROOT}
render={() => <Redirect to={URLS.DASHBOARD} />}
/>
<Authenticated
{...props}
resource={ResourcesCode.DASHBOARD}
patent={PatentsCode.VIEW}
path={URLS.DASHBOARD}
component={Dashboard}
/>
<Authenticated
{...props}
resource={ResourcesCode.SUBSCRIBE}
patent={PatentsCode.VIEW}
path={URLS.SUBSCRIBE}
component={Subscribe}
/>
</Fragment>
</BrowserRouter>
// ...
Inside of the component Subscribe (mentioned above in the 2nd Authenticated component), I have more routes as you can see below:
// ...
<Route
path={URLS.SUBSCRIBE}
exact
render={() => (
//...
)}
/>
<Route
path={URLS.SUBSCRIBETWO}
exact
render={() => (
//...
)}
/>
// ...
The point is that this routes on the child component (Subscribe) are ignored.
Why are them ignored? How can I solve it?
I really need this routes inside the child component. I don't want to move them to App.js
IMPORTANT EDIT:
The second route is ignored, I realized that the first doesn't. In other words, The Route component with path={URLS.SUBSCRIBE} is working, but the component with path={URLS.SUBSCRIBETWO} is ignored, so here is the problem to solve.
EDIT:
For if you need, the Authenticated component:
// ...
}) => (
<Route
path={path}
exact={exact}
render={route => {
if (!authenticated) {
if (loggingIn) {
return '';
}
return <Redirect to={URLS.LOGIN} />;
}
if (!roleSubReady) {
return '';
}
if (path !== URLS.SUBSCRIBE && user.pendingSubscription) {
if (isLoading) {
return '';
}
return <Redirect to={URLS.SUBSCRIBE} />;
}
if (path === URLS.SUBSCRIBE && !user.pendingSubscription) {
if (isLoading) {
return '';
}
return <Redirect to={URLS.DASHBOARD} />;
}
if (resource && !checkPermission(user, resource, patent)) {
return <NotAuthorized history={route.history} />;
}
return (
<React.Fragment>
<Menu user={user} path={path} isLoading={isLoading} />
<Header show={showHeaderAndFooter} user={user} path={path} />
<MainContent>
{React.createElement(component, {
user,
resource,
...route,
})}
<Footer show={showHeaderAndFooter} />
</MainContent>
</React.Fragment>
);
}}/>
);

Categories

Resources