React Router V6 nested Routes with i18n - javascript

I have a question about React Router V6 nested with i18n.
This is my first multi-language service.
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path={`/`} element={<Home />}>
<Route path={`${lang}`}>
<Route path={`service`}>
<Route path={'slack'} element={<Slack />} />
</Route>
</Route>
{/* <Route path={`service/dooray`}element={<Dooray />} /> */}
{/* <Route path={`contact`} element={<Contact />} /> */}
{/* <Route path={`app/sign-in`} element={<SignIn />} /> */}
{/* <Route path={`app/sign-up`} element={<SignUp />} /> */}
{/* <Route path={`app/mail-code`} element={<MailCode />} /> */}
{/* <Route path={`app/password/reset`} element={<PwdReset />} /> */}
{/* <Route path={`policies/privac`} element={<Privacy />} /> */}
{/* <Route path={`policies/terms`} element={<Terms />} /> */}
</Route>
{/* <Route path={`*`} element={<>NOT FOUND</>} /> */}
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
If I enter localhost:3000/en, there is an error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.'
How can I fix it..
I want /en => go to english page, /jp => go to japanese page
const MainPage:React.FC =() => {
...
<Route path={`/`} element={<Home />}>
<Route path={`/${lang}/*`}>
<Route path={`service`}>
<Route path="slack" element={<Slack />} />
</Route>
</Route>
</Route>
}
const Home:React.FC = () => {
return (
<>
... UI, JSX
<Outlet />
</>
)
}
I add a <Outlet />. But if I entered '/ko/service/slack', render <Home /> now
<Route path={`/`} element={<Home />}>
<Route path="service">
<Route path="slack" element={<Slack />} />
<Route path="dooray" element={<Dooray />} />
</Route>
</Route>
nested-routes doesn't work.. :(

I had the exact same useCase (localize react router v6) and came up with the following LangRouter repository link:
const LangRouter = () => {
const { i18n } = useTranslation(),
{ pathname, search, hash } = useLocation(),
navigate = useNavigate(),
availableLocales = ['en', 'ar'],
defaultLocale = (
getDefaultLanguage() === 'en' || getDefaultLanguage() === 'ar' ? getDefaultLanguage() : 'en'
) as string,
pathnameLocale = pathname.substring(1, 3).toLowerCase(),
[locale, setLocale] = useState(defaultLocale),
loaderTimerRef = useRef<any>(),
[isLoading, setIsLoading] = useState(true);
useEffect(() => {
loaderTimerRef.current = setTimeout(() => {
setIsLoading(false);
clearTimeout(loaderTimerRef.current);
}, 300);
}, []);
useEffect(() => {
if (availableLocales.includes(pathnameLocale)) {
updateLocale(pathnameLocale);
} else if (pathname === '/') {
updateLocale(defaultLocale);
}
// eslint-disable-next-line
}, [pathname]);
useEffect(() => {
let lang = defaultLocale;
if (availableLocales.includes(pathnameLocale)) {
lang = pathnameLocale;
setLanguageHandler(lang);
} else if (pathname === '/') {
setLanguageHandler(lang);
}
// eslint-disable-next-line
}, [locale]);
const setLanguageHandler = (lang: string) => {
if (lang === 'en') {
i18n.changeLanguage('en-US');
} else {
i18n.changeLanguage('ar-SA');
}
};
const updateLocale = (newLocale: string) => {
const newPath = `/${newLocale}` + pathname.substring(3);
if (locale !== newLocale) {
if (newPath === `/${newLocale}/` || newPath === `/${newLocale}` || pathname === '/') {
navigate(getHomePageUrl(newLocale));
} else {
navigate(`${newPath}${hash}${search}`);
}
setLocale(newLocale);
} else if (newPath === `/${newLocale}/` || newPath === `/${newLocale}` || pathname === '/') {
if (isAuthenticated()) {
navigate(getHomePageUrl(newLocale));
} else {
navigate(getLoginPageUrl(newLocale));
}
}
};
if (isLoading) {
return (
<div className="loader-wrapper">
<LoadingIcon />
</div>
);
}
return (
<LocaleContext.Provider value={{ locale, setLocale: updateLocale }}>
<Routes>
<Route path={`/${locale}`} element={<App />}>
{publicRoutes.map((el, i) => (
<Route
key={i}
path={el.path(locale)}
element={
<PublicRouteGuard
restricted={el.restricted}
redirect={el.redirect ? el.redirect(locale) : undefined}
>
{el.element}
</PublicRouteGuard>
}
/>
))}
{privateRoutes.map((el, i) => (
<Route
key={i}
path={el.path(locale)}
element={
el.permissions ? (
<RestrictedRouteGuard requiredPermissions={el.permissions}>
{el.element}
</RestrictedRouteGuard>
) : (
<PrivateRouteGuard>{el.element}</PrivateRouteGuard>
)
}
>
{el.children &&
el.children.map((innerEl, innerI) => (
<Route key={innerI} path={innerEl.path(locale)} element={innerEl.element} />
))}
</Route>
))}
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</LocaleContext.Provider>
);
};
export default LangRouter;

Issue
The error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.' means the parent route isn't rendering an Outlet component for the nested routes to be rendered into. The route rendering the Home component doesn't appear to be rendering an Outlet.
Solution
Update the Home component to render an Outlet. Note that Route components without an element prop will render an Outlet by default.
Example:
import { Outlet } from 'react-router-dom';
const Home = () => {
...
return (
<>
... home page UI/JSX ...
<Outlet />
</>
);
};
...
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path="/" element={<Home />}>
<Route path={lang}> // <-- renders Outlet by default
<Route path="service"> // <-- renders Outlet by default
<Route path="slack" element={<Slack />} />
</Route>
</Route>
...
</Route>
...
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
);
};
Update
If the Home and Slack components are separate and independent, then move the Home component into an index route and simplify the routing to the Slack component.
<Routes>
<Route path="/">
<Route index element={<Home />} />
<Route path={`${lang}/service/slack`} element={<Slack />} />
</Route>
</Routes>

Related

Route inside route not work for me in react

I have a website that I'm building and an admin dashboard that I want to go to. I have a page that has an <Outlet/> inside it, and only when I go to the admin-dashboard address it works, but if I add a path, it leaves me from the page where I have the <Outlet/>.
This is my page with <Outlet/> code:
const Layout = (props) => {
const [open, setOpen] = useState(false);
const dreWidth = window.screen.width > 1000 ? open ? 300 : 100 : null
return (
<Box>
{props.adminPath && <MiniDrawer open={open} setOpen={setOpen} logged={props.logCheck} auth={props.auth}/>}
<Box
component='main'
style={{ flexGrow: 1, padding: 3, width: `calc(100% - ${dreWidth}px)` , transition : '0.5s' , float: 'left' }}
>
<Toolbar style={{ minHeight: "1.875rem" }} />
<Box style={{ margin: "1rem 2rem" }}>
<Outlet />
</Box>
</Box>
</Box>
);
};
export default Layout;
This is main with the Routers :
const MainPage = () => {
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth.auth);
const token = useSelector((state) => state.auth.token);
const users = useSelector((state) => state.users.users);
const { pathname } = useLocation()
useEffect(() => {
dispatch(LoadUser())
dispatch(getAllUsers())
}, [dispatch])
let logCheck = token
let user = users.find(user => user.id === auth.id)
let adminPath = pathname === '/admin-dashboard'
return (
<>
<Box>
{!adminPath && <Header logged={logCheck} user={user} auth={auth}/>}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<Contact />} />
<Route path="/join-to-course" element={logCheck ? <Navigate to='/'/> :<JoinToCourse />} />
<Route path="/join" element={logCheck ? <Navigate to='/'/> : <MainJoinToCourse />} />
<Route path="/join/:orderId" element={logCheck ? <Navigate to='/'/> : <PaymentPage />} />
<Route path="/login" element={logCheck ? <Navigate to='/'/> : <LoginPage />} />
<Route path="admin-dashboard" element={logCheck ? <Layout adminPath={adminPath} user={user} auth={auth}/> : <Navigate to='/' />}>
<Route path="" element={<Dashboard />}/>
<Route path="home-page" element={<HomePageContents />}/>
</Route>
</Routes>
</Box>
<Box>
<BackToTop />
</Box>
{!adminPath && <Footer />}
</>
);
};
export default MainPage;
Doesn't matter I just changed the code
from this :
let adminPath = pathname === '/admin-dashboard'
to this :
let adminPath = pathname.includes('/admin-dashboard')
now its work
As I understand your question/issue you appear to want to conditionally render the Header component on non-admin routes.
Create another layout that renders Header and an Outlet, similar to the existing Layout component.
Non-Admin layout
const Layout = () => {
const auth = useSelector((state) => state.auth.auth);
const token = useSelector((state) => state.auth.token);
const users = useSelector((state) => state.users.users);
const user = users.find(user => user.id === auth.id);
return (
<>
<Header logged={token} user={user} auth={auth}/>
<Box style={{ margin: "1rem 2rem" }}>
<Outlet />
</Box>
<Box>
<BackToTop />
</Box>
<Footer />
</>
);
};
Admin layout
const AdminLayout = () => {
const auth = useSelector((state) => state.auth.auth);
const token = useSelector((state) => state.auth.token);
const users = useSelector((state) => state.users.users);
const [open, setOpen] = useState(false);
const dreWidth = window.screen.width > 1000 ? open ? 300 : 100 : null
return (
<Box>
<MiniDrawer open={open} setOpen={setOpen} logged={token} auth={auth} />
<Box
component='main'
style={{
flexGrow: 1,
padding: 3,
width: `calc(100% - ${dreWidth}px)`,
transition : '0.5s',
float: 'left'
}}
>
<Toolbar style={{ minHeight: "1.875rem" }} />
<Box style={{ margin: "1rem 2rem" }}>
<Outlet />
</Box>
</Box>
</Box>
);
};
It seems you are also using a token value stored in state to conditionally render a routed component or the Navigate component. For simplicity of routing I'd suggest also creating protected layout routes as well.
ProtectedRoute
import { Outlet } from 'react-router-dom';
const ProtectedRoute = () => {
const token = useSelector((state) => state.auth.token);
if (token === undefined) {
return null;
}
return token ? <Outlet> : <Navigate to='/' replace />;
};
AnonymousRoute
import { Outlet } from 'react-router-dom';
const AnonymousRoute = () => {
const token = useSelector((state) => state.auth.token);
if (token === undefined) {
return null;
}
return token ? <Navigate to='/' replace /> : <Outlet>;
};
Main
const MainPage = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(LoadUser());
dispatch(getAllUsers());
}, [dispatch]);
return (
<Box>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<Contact />} />
<Route element={<AnonymousRoute />}>
<Route path="/join-to-course" element={<JoinToCourse />} />
<Route path="/join" element={<MainJoinToCourse />} />
<Route path="/join/:orderId" element={<PaymentPage />} />
<Route path="/login" element={<LoginPage />} />
</Route>
</Route>
<Route element={<ProtectedRoute />}>
<Route path="admin-dashboard" element={<AdminLayout />}>
<Route index element={<Dashboard />} />
<Route path="home-page" element={<HomePageContents />} />
</Route>
</Route>
</Routes>
</Box>
);
};

Unable to display information after using React Routes with "Lifting state up"

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;

React router not working on live production

I have built a UI amazon-clone with create-react-app
it only shows dummy data.
the problem is after publishing it to Vercel, the routing not working as expected! after clicking the links you see a blank page "URL params are correct", you have to manually reload the page to work!
also if you clicked a button no event trigger and you get a blank page!
I wrapped all my routes to MainRoute Component:
const MainRoute = withRouter(({ location }) => {
return (
<>
{location.pathname !== "/login" && location.pathname !== "/signup" ? (
<Header />
) : null}
<Switch>
<Route exact path="/" render={() => <Home />} />
<Route exact path="/products" render={() => <Products />} />
<Route
exact
path="/products/:productID"
render={() => <ProductPage />}
/>
<Route path="/checkout" render={() => <Checkout />} />
<Route path="/payment" render={() => <Payment />} />
<Route path="/login" render={() => <Login />} />
<Route path="/signup" render={() => <Signup />} />
<Route render={() => <NotFoundPage />} />
</Switch>
{location.pathname !== "/login" && location.pathname !== "/signup" ? (
<Footer />
) : null}
</>
);
});
export default withRouter(MainRoute);
my App Component:
function App() {
return (
<div className="app_wrapper">
<Router>
<MainRoute />
</Router>
</div>
);
}
export default App;
repo
https://github.com/aseelban/amazon-clone-app
link:
https://amazon-clone-app-llyl1tfcn.vercel.app/
it works correctly (under Brave browser) the authentication routes, could you please specify which route the issue occurs.!
Thanks, everyone for the help.
I solved this problem by removing HOC withStyles and instead use react-jss.

iterating on react components

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

#reach/router nested protected route

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

Categories

Resources