Passing parameters to dynamically generated react routes - javascript

I'm using react-router-dom and generate my routes dynamically from an array like so
Routes Data
const routes = [
{
path: '/',
exact: true,
component: () => <MyComponent />
}
}
Route Generation
{routes.map((route, index) => {
return <Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
})}
Render Prop
I found out about the render() prop I could define but even so how do I do this since the component is inside a variable
const props = this.props;
{routes.map((route, index) => {
return <Route
key={index}
path={route.path}
exact={route.exact}
render={(props) => <??? {...props} />
/>
})}
How can I pass this.props during the route generation?

You could change your array to just include the component instead of a new function component that renders the component and you will get the props sent to it:
const routes = [
{
path: '/',
exact: true,
component: MyComponent
}
}
You can use any capitalized variable as a component, so you could also do this if you prefer:
{routes.map((route, index) => {
const Component = route.component;
return <Route
key={index}
path={route.path}
exact={route.exact}
render={(props) => <Component {...props} />
/>
})}

Related

Managing clicked item's props in a new route - React router v6

I have a map of an objects. Each object is an item in a list. When I click on a concrete item it moves me to a specific route with that item's id. How to properly manage that concrete item's props inside new route?
Is this the way to go or there is better way?
export const Router = (): ReactElement => {
const [itemDetails, setItemDetails] = useState<Item>();
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Root />}>
<Route
index
element={<ItemList setItemDetails={setItemDetails} />}
/>
<Route
path="/item/:itemId"
element={<ItemDetails itemDetails={itemDetails} />}
/>
</Route>
</Routes>
</BrowserRouter>
);
};
So basically what I'm doing here is I'm passing state setter function to a list where my mapped items are stored so when I click on a concrete one I save its props to that function.
Then I just pass updated state to a new route where details of a concrete item should be displayed.
If you are asking if there's a better way than pushing some value from ItemList up to the parent state to be passed down to ItemDetails, then sure, a more React/react-router-dom way would be to hoist the items array from the ItemList component up to this parent component and pass the data down to children routes as props or via a context.
Example using props
export const Router = (): ReactElement => {
const [items, setItems] = useState<Array<Item>>([]);
... logic to populate items state ...
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Root />}>
<Route index element={<ItemList items={items} />} />
<Route path="/item/:itemId" element={<ItemDetails items={items} />} />
</Route>
</Routes>
</BrowserRouter>
);
};
ItemDetails
const ItemDetails = ({ items }) => {
const { itemId } = useParams();
const item = items.find(item => String(item.id) === itemId);
if (!item) {
return <div>No matching item found.</div>;
}
return (
... render item out to JSX
);
};
Example using Outlet context
export const Router = (): ReactElement => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Root />}>
<Route index element={<ItemList />} />
<Route path="/item/:itemId" element={<ItemDetails />} />
</Route>
</Routes>
</BrowserRouter>
);
};
Root
const Root = () => {
const [items, setItems] = useState<Array<Item>>([]);
... logic to populate items state ...
return (
...
<Outlet context={{ items }} />
...
);
};
ItemList
const ItemList = () => {
const { items } = useOutletContext();
return (
... map items to JSX
);
};
ItemDetails
const ItemDetails = () => {
const { items } = useOutletContext();
const { itemId } = useParams();
const item = items.find(item => String(item.id) === itemId);
if (!item) {
return <div>No matching item found.</div>;
}
return (
... render item out to JSX
);
};

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 },
}}
/>
)
}
/>
);
};

In below given 2 examples they are almost similar but still one's exact keyword works but others wont why?

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);

React router 4 render same component for all routes

So this question is more about the approach I should take to solve this problem.
I have a JSON like this
const app = [
{
tab: 'Home',
// content: '<div>This is home</div>',
nav: [
{
tab: 'Dashboard',
content: '<div>This is Dashboard</div>'
},
{
tab: 'Profile',
content: '<div>This is Profile</div>'
},
]
},
{
tab: 'About',
content: '<div>This is About</div>'
},
{
tab: 'Pricing',
content: '<div>This is Pricing</div>'
},
];
Now what I would want is setup the entire routes and pages to render the above JSON.
Below is the pseudo code:
Approach 1...
Loop through and for each nav/subnav add Route.
import {
BrowserRouter,
Route,
NavLink,
} from 'react-router-dom';
const Page = (content) => {
return (
<div>{content}</div>
)
}
<BrowserRouter>
app.map((nav, i) =>
<NavLink key={nav.tab} to={`/${nav.tab}`} activeClassName="active">
{nav.tab}
</NavLink>
<Route
path={`/${nav.tab}`} render={(props) => (
<Page {...props} pageData={nav.content}>
if(nav.subNav) {
nav.subNav.map((subnav, i) =>
<NavLink key={subnav.tab} to={`/${nav.tab}/${subnav.tab}`} activeClassName="active">
{nav.tab}
</NavLink>
<Route
path={`/${nav.tab}/${subnav.tab}`} render={(props) => (
<Page {...props} pageData={subnav.content} />
)}
/>
)
}
</Page>
)}
/>
)
</BrowserRouter>
Approach 2
Would something like this work/be better? Just have 2 routes returning same component, pass the app object to Page and then based on URL render the corresponding data
const Page = (app) => {
return (
// Loop over app
if(mainNav from URL == tab) {
if(subNav from URL == tab) {
<div>{content}</div>
}
}
)
}
<BrowserRouter>
<Route
path={'/:mainNav'} render={(props) => (
<Page {...props} pageData={app} />
)}
/>
<Route
path={'/:mainNav/:subNav'} render={(props) => (
<Page {...props} pageData={app} />
)}
/>
</BrowserRouter>
Thanks for reading through and appreciate your help. If theres another better approach I would love to know!
Try utilizing the exact prop offered in your <Route /> components.
<Route exact path="/foo" component={Foo} />
If you have the routes:
/foo
/foo/bar
/foo/bar/baz
/foo matches in all three cases.

Redirect won't work with react-router v4

I got this warning
Warning: You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored
Not sure it's causing the Redirect to fail, but my code below just won't work, it's fine if this.isAuth is true, but not with <Redirect />
https://codesandbox.io/s/5xm202p1j4
render() {
const { Component: component, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
this.isAuth === false ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login"
}}
/>
)
}
/>
);
}
You have destructured your props incorrectly, it should be const { component: Component, ...rest } = this.props;
The reason that it was giving you the warning was because you tried to destructure Component with uppercase C from props whereas you pass component as props to the authRoute, and not since Component is now present in props the rest params contains the component props which is passed down to the Route.
render() {
const { component: Component, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
this.isAuth === false ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login"
}}
/>
)
}
/>
);
}
Working sandbox

Categories

Resources