I'm newbie with react but i saw something weird. I've got all of my routes something like below:
<Main>
<Route exact path="/home" component={Home} />
<Route exact path="/home1" component={Home1} />
<Route exact path="/home2" component={Home2} />
<Route exact path="/home3" component={Home3} />
<Route exact path="/home4" component={Home4} />
<Route exact path="/home5" component={Home5} />
<Redirect from="*" to="/home" />
</Main>
is there any way to use an array and map instaed of paste another one <Route /> component?
I think about somehting like this:
routes.ts
const routes = [
{ path: "/home", component: SomeComponent },
{ path: "/home2", component: SomeComponent2 },
];
indes.tsx
<Main>
{ routes.map(route => (
<Route exact path={route.path} component={route.component} />
))}
</Main>
but i don't know how to pass new component into object in routes list.
Thanks for any help!
Note: Which react-router-dom version are u using? This is a code example for RRD6
const routes = [
{ name: "/route1", component: <ClickLabel /> },
{ name: "/route2", component: <Focus /> },
{ name: "/route3", component: <Counter /> }
];
/*--------------***/
{routes.map(({ name, component }, i) => (
<Route key={i} path={name} element={component} />
))}
Here is example:
https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/App.js
If u are using RRDv5, then here is the code
{/* Want to pass some dynamic props */}
{routes.map(({ name, component }, i) => (
<Route
key={i}
path={name}
render={() => {
return React.createElement(
component,
{ p: "THis is the prop" },
null
);
}}
/>
))}
{/* Simply render a component */}
{routes.map(({ name, component }, i) => (
<Route key={i} path={name} component={component} />
))}
Related
I'm learning React Router from textbook and facing a problem when changing from Switch to Routes with the new function of Lifting State Up. I can't display text after clicking the 'News' link. Here shows the code:
/* index.jsx */
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<HashRouter>
<Routes>
<Route path='/home' element={<Home />} />
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/news' element={<News />} />
</Routes>
</HashRouter>,
);
/* About.jsx */ <- good code
const About = () => (
<div>
<Menu />
<h1>這裡是關於我們 This is about us.</h1>
</div>
);
export default About;
/* News.jsx */ <- Problem comes in.
const News = () => {
const [news] = useState([
{ id: 1, name: 'News1', describe: 'A wins!' },
{ id: 2, name: 'News2', describe: 'B wins!' },
]);
return (
<div>
<Menu />
<Routes>
<Route path='/news/*' element={<><p>hello!</p><NewsList
news={news} /></>} />
<Route path='newsReader/:id' element={<><NewsReader news ={news} /></>} />
</Routes>
</div>
);
};
export default News;
It should be like this:
I believe the problem lies in <Route path> but can't find the solution.
The code <NewsList> and <NewsReader> is here:
const NewsList = props => (
<ul>
{
props.news.map(theNews => (
<li key = {theNews.id}>
<Link to = {`/news/newsReader/${theNews.id}`}>
{theNews.name}</Link>
</li>
))
}
</ul>
);
export default NewsList;
const NewsReader = (props) => {
const {id:targetNewsId} = useParams();
const targetNews = props.news.find(theNews => (
String(theNews.id) === String(targetNewsId)
));
return(
<div>
<h1>你正在閱讀 You are now reading {targetNews.name}</h1>
<p>{targetNews.describe}</p>
</div>
)
}
export default NewsReader;
Thank you very much for your answer.
For nested routes, you need to follow the below approach
// index.js -> /news/*
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<HashRouter>
<Routes>
<Route path='/home' element={<Home />} />
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/news/*' element={<News />} /> // here you need to /*
</Routes>
</HashRouter>,
);
For the News.jsx you need to have relative path to /news like below
const News = () => {
const [news] = useState([
{ id: 1, name: 'News1', describe: 'A wins!' },
{ id: 2, name: 'News2', describe: 'B wins!' },
]);
return (
<div>
<Menu />
<Routes>
<Route path='/*' element={<><p>hello!</p><NewsList
news={news} /></>} />
<Route path='newsReader/:id' element={<><NewsReader news ={news} /></>} />
</Routes>
</div>
);
};
export default News;
I'm developing react app use react-router-dom. I have unusual the app behavior. The app doesn't have home page. We can go to the app use 'slug'. For example: http://siteExample.net/my-subsite. If my-subsite exist in our database We need to work with this slug.
My code snippet for routes:
const App = ({ isAuth, onSetSlug, slug}) => {
useEffect(() => {
onSetSlug(window.location.pathname.split('/')[1])
}, [])
return (
<Router>
<div className="App">
<Suspense fallback={<Loading />}>
<MainModal />
<ScrollToTop>
{!isAuth ? (
<Switch>
<Route path={'/:slug'} exact component={lazy(() => import('./containers/notAuth/home'))} />
<ContentWrapper
container
component="main"
adaptSidebar={5}
adaptContent={7}
>
<Switch>
<Route path={'/:slug/login'} exact component={lazy( () => import('./containers/notAuth/login'))} />
<Route path={'/:slug/signup'} exact component={lazy(() => import('./containers/notAuth/signup'))} />
<Redirect to={'/:slug'} />
</Switch>
</ContentWrapper>
<Redirect to={'/:slug'} />
</Switch>
</ScrollToTop>
</Suspense>
</div>
</Router>
)}
There is a issue that :slug/login doesn't replace on my-subsite/login for example.By the way it's working only for my local machine. When I publish it to remote host it doesn't work completely.
Remove the exact prop on the outer/root route since it necessarily excludes matching any subroutes.
<Switch>
<Route
path={'/:slug'} // <-- want this path prefix to match
component={lazy(() => import('./containers/notAuth/home'))}
/>
<ContentWrapper
container
component="main"
adaptSidebar={5}
adaptContent={7}
>
<Switch>
<Route
path={'/:slug/login'}
exact
component={lazy( () => import('./containers/notAuth/login'))}
/>
<Route
path={'/:slug/signup'}
exact
component={lazy(() => import('./containers/notAuth/signup'))}
/>
<Redirect to={'/:slug'} />
</Switch>
</ContentWrapper>
<Redirect to={'/:slug'} />
</Switch>
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 },
}}
/>
)
}
/>
);
};
i wanted to iterate on the list of components as you can there is lot of redundancy so for example i have long list of routes just as given below
<Route
exact
path={Routes.HOME}
render={() => (
<LandingPage
setValue={setValue}
setSelectedIndex={setSelectedIndex}
/>
)}
/>
<Route
exact
path={Routes.SERVICES}
render={() => (
<ServicesPage
setValue={setValue}
setSelectedIndex={setSelectedIndex}
/>
)}
/>
<Route
exact
path={Routes.MOBILE_APPS}
render={() => (
<MobileAppsPage
setValue={setValue}
setSelectedIndex={setSelectedIndex}
/>
)}
/>
i would create a list like below
const list = [
{path: Routes.HOME, component: LandingPage},
{path: Routes.SERVICES, component: ServicesPage},
{path: Routes.MOBILE_APPS, component: MobileAppsPage}];
and i want to achieve below
list.map((obj) => (
<Route
exact
path={obj.path}
render={() => (
<obj.component
setValue={setValue}
setSelectedIndex={setSelectedIndex}
/>
)}
/>
))
When rendering a custom component, you must have its name Capitalized:
// Good
<Component/>
<Obj.Component/>
<Obj.component/>
// Bad
<component/>
<obj.component/>
<obj.Component/>
list.map(({ path, component: Component }) => (
<Route
exact
path={path}
render={() => (
<Component setValue={setValue} setSelectedIndex={setSelectedIndex} />
)}
/>
));
// Same
list.map((obj) => {
const { component: Component, path } = obj;
return <Route path={path} render={() => <Component {...}/>} />;
});
I use reach/router with custom protected route like this :
const ProtectedRoute = ({ component: Component, ...rest }) => (
localStorage.getItem('user_id') ? <Component {...rest} /> : <Redirect from="" to="/login" noThrow />
);
const LoginRoute = ({ component: Component, ...rest }) => (
localStorage.getItem('user_id') ? <Redirect from="" to="/home" noThrow /> : <Component {...rest} />
);
const PublicRoute = ({ component: Component, ...rest }) => (
<Component {...rest} />
);
<Router>
<LoginRoute component={Login} path="/login" />
<PublicRoute default component={Notfound} />
<ProtectedRoute component={landingPage} path="/home" />
<ProtectedRoute path="/" component={App} />
<ProtectedRoute component={UserList} path="user" />
<ProtectedRoute component={UserDetail} path="user/create" />
</Router>
i want this to be nested route with user/:id
<ProtectedRoute component={UserList} path="user" />
<ProtectedRoute component={UserDetail} path="user/create" />
what should i do?
I Feel like you're complicating things
const Routes = () => {
const [loggedIn, setLoggedIn] = useState(false)
useEffect(() => {
localStorage.getItem('user_id') && setLoggedIn(true)
}, [])
return (
<Router>
<LoginRoute component={Login} path="/login" />
<Notfound default />
{
loggedIn
? (
<>
<LandingPage path="/home" />
<App path="/" component={App} />
<UserList component={UserList} path="user">
<UserDetail component={UserDetail} path="create" />
</UserList>
</>
) : (
<Redirect from="" to="/login" noThrow />
)
}
</Router>
)
}
In case this didn't work as intended or you feel you want to use it in your way, do this
<Router>
...
<ProtectedRoute component={UserList} path="user">
<UserDetail path="create" />
</ProtectedRoute>
</Router>
No need of using ProtectedRoute HOC for UserDetail since it's already nested under ProtectedRoute
and in UserList use props.children to render UserDetail