As per me, I have a bit complex routing because I have to handle different domain for different modules. That is why i configured the routes in a following way.
Here it is
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Root />
</ConnectedRouter>
</Provider>,
MOUNT_NODE,
);
class App extends React.Component {
render() {
return (
<Switch>
<UnauthenticatedRoute path="/auth" component={AsyncLogin} {...this.props} />
<AuthenticatedRoute path="/" component={AsyncHome} {...this.props} />
</Switch>
);
}
}
class Home extends React.Component<propsCheck> {
componentDidMount() {
this.props.getUser();
}
renderRoutes(userRole, roles, userData, props) {
const domain = window.location.hostname; // domain will be like app.abc.com, app.def.com.
switch (domain) {
case GROWTH_URL:
return growthRoutes(userRole, roles, userData, props);
case CONTENT_URL:
return contentRoutes(userRole, roles, userData, props);
default:
return growthRoutes(userRole, roles, userData, props);
}
}
render() {
if (this.props.loading) {
return <Spinner background="none" />;
}
return <Switch>{this.renderRoutes(userRole, roles, userData, this.props)}</Switch>;
}
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
)(Home);
export default withRouter(withConnect);
function NotFoundPage() {
return <div>Not found</div>;
}
export function growthRoutes(userRole, roles, userData, props) {
return (
<Switch>
<Route
exact
path="/"
render={() =>
(!isEmpty(userRole) && userRole.client !== null && isClient(roles)) ||
(!isEmpty(userData) && userData.client !== null && isClient(userData.roles)) ? (
<Redirect to={`${!isEmpty(userRole) ? userRole.client[0].company_slug : userData.company[0]}`} />
) : (
<Redirect to="/clients" />
)
}
/>
<DashboardRoute path="/clients" component={Clients} {...props} />
<DashboardRoute path="/:company/" component={ClientDetail} {...props} />
<DashboardRoute path="/:company/client_detail" component={ClientDetail} {...props} />
<DashboardRoute path="/:company/edit-client" component={Admin(Client)} {...props} />
<DashboardRoute path="/tasks" component={Tasks} {...props} />
<DashboardRoute to="*" component={NotFoundPage} />
</Switch>
);
}
I could not show NotFoundPage this way and could not figure out why it is not working. I have no idea where should i use the snippet <Route path="*" component={NotFoundPage}>. Nowhere it works. Can anyone look at this, please?
The problem is with respect to how you define your routes. since you have a Route /:company/, it will match anything which starts with / like /abc/, /abc/sfsf/fsgs and so on
So what you need to do is to first reorder your Routes, so that all Routes within Switch component work like
<Switch>
<Route
exact
path="/"
render={() =>
(!isEmpty(userRole) && userRole.client !== null && isClient(roles)) ||
(!isEmpty(userData) && userData.client !== null && isClient(userData.roles)) ? (
<Redirect to={`${!isEmpty(userRole) ? userRole.client[0].company_slug : userData.company[0]}`} />
) : (
<Redirect to="/clients" />
)
}
/>
<DashboardRoute path="/clients" component={Clients} {...props} />
<DashboardRoute path="/tasks" component={Tasks} {...props} />
<DashboardRoute path="/:company/client_detail" component={ClientDetail} {...props} />
<DashboardRoute path="/:company/edit-client" component={Admin(Client)} {...props} />
<DashboardRoute exact path="/:company" component={ClientDetail} {...props} />
<DashboardRoute to="*" component={NotFoundPage} />
</Switch>
Now in the above case routes like /abc/ffg/ will show NotFoundPage but still Routes like /abc will still match /:company where company will be abc. So what you need to do in ClientDetail, check if the company is a valid company and return the Correct view and if it isn't you return the NotFoundPage
ClientDetails
render() {
if(!validCompanies.includes(this.props.match.company)) {
return <DashboardRoute to="*" component={NotFoundPage} />
}
// Normal component logic here
}
Related
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 },
}}
/>
)
}
/>
);
};
Example 1.
routes.js(1st file)
<ProtectedRoutes path={prop.path} component={prop.component} key={key} />
protectedRoute.js(2nd file)
<Route
{...rest}
exact
render={(props) => {
if (verified) return <Component {...props} />;
return (
<Redirect
to={{
pathname: routes.LICENCEKEY,
}}
/>
);
}}
/>
Example 2.
routes.js(1st file)
<ProtectedRoutes exact path={prop.path} component={prop.component} key={key} />
protectedRoute.js(2nd file)
<Route
{...rest}
render={(props) => {
if (verified) return <Component {...props} />;
return (
<Redirect
to={{
pathname: routes.LICENCEKEY,
}}
/>
);
}}
/>
My question is that the first example is not working but the second example is working.
The only difference is that in 1st example I'm writing exact directly inside and in 2nd example I'm passing exact to protectedRoute component and receiving and applying as rest.
Let me know if u understood my question.
EDIT
full protectedRoute.js file
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
import routes from 'constants/routes.json';
const ProtectedRoute = ({ verified, component: Component, ...rest }) => {
return (
<Route
exact
{...rest}
render={(props) => {
if (verified) return <Component {...props} />;
return (
<Redirect
to={{
pathname: routes.LICENCEKEY,
}}
/>
);
}}
/>
);
};
const mapStateToProps = (state) => {
return {
verified: state.licencekey.verified,
};
};
export default connect(mapStateToProps)(ProtectedRoute);
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} />)
}} />
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>
);
}}/>
);
I'm using React v4.2 for my app, and it seems not to be matching the correct path for the routes:
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route path="/login" render={(props) => {
if (isAuthenticated()) {
return <Redirect to='/' />;
} else {
return <LoginForm {...props}/>
}
}
} />
<EnsureLoggedInContainer>
<Route exact path="/group" render={(props) => {
debugger;
return <GroupList {...props}/>
}
}/>
<Route exact path="/group/new" render={(props) => {
debugger;
return <GroupList {...props} modal={rr}/>;
}
} />
</EnsureLoggedInContainer>
</Switch>
</div>
</BrowserRouter>
</Provider>
I have some links in the app, on which I click and I redirect the client to new URL:
_onSubmit(values) {
const { history } = this.props;
this.props.createGroup(values, ({status}) => history.push('/group'));
}
I inspect the values of props.history.location.pathname and props.match.path and they don't match. Why is this happening? Why is the correct route not matched?
Update 1
The code for EnsureLoggedInContainer:
class EnsureLoggedInContainer extends Component {
componentDidMount() {
if (!isAuthenticated()) {
dispatch(setRedirectUrl(currentURL))
this.props.history.replace("/login")
}
}
render() {
if (isAuthenticated()) {
return(
<div>
<AppNavBar />
<ComponentsNavBar />
{this.props.children}
</div>
);
} else {
return <noscript />;
}
}
}
export default EnsureLoggedInContainer;
Update 2
I changed the router configuration code to the following:
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route exact path="/login" render={(props) => {
if (isAuthenticated()) {
return <Redirect to='/' />;
} else {
return <LoginForm {...props}/>
}
}
} />
<Route exact path="/register" render={(props) => {
if (isAuthenticated()) {
return <Redirect to='/' />;
} else {
return <RegisterForm {...props}/>
}
}
} />
<EnsureLoggedInContainer>
<Route exact path="/group" component={GroupList} modal={newGroupModal}/>
<Route exact path="/group/new" component={GroupList}/>
<Route component={Home} />
</EnsureLoggedInContainer>
</Switch>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
And changed the last line of EnsureLoggedInContainer to :
export default withRouter(EnsureLoggedInContainer);
But still, I get Home always being rendered, and random URLs being matched to unrelated routes (e.g. /group/new)
As per the documentation:
A match object contains information about how a matched
the URL. match objects contain the following properties:
params - (object) Key/value pairs parsed from the URL corresponding to the dynamic segments of the path
isExact - (boolean) true if the entire URL was matched (no trailing characters)
path - (string) The path pattern used to match. Useful for building nested <Route>s
url - (string) The matched portion of the URL. Useful for building nested <Link>s
while
Locations represent where the app is now, where you want it to go, or
even where it was.
so if you are on say /group/new, location.pathname will give you /group/new whereas match.path will give your the Route path defined for the component in which you log it if it is matched
I finally managed to resolve the issue by using the private route pattern.