Below code displays a list of items.
App.js
render() {
return (
<Router>
<div className="App">
<AppContext.Provider value={{ state: this.state }}>
<ItemList />
<Switch>
<Route path="/detail/:id" component={ItemDetail} />
<Route exact={true} path="/" component={ItemList} />
</Switch>
</AppContext.Provider>
</div>
</Router>
);
}
ItemList.js
render() {
return (
<AppContext.Consumer>
{context => {
return context.state.itemList.map(item => {
return (
<Link key={item.id} to={`/detail/${item.id}`}>
<p>{item.id}</p>
</Link>
);
});
}}
</AppContext.Consumer>
);
}
ItemDetail.js
import React from "react";
export default function ItemDetail(props) {
return <div>Item detail</div>;
}
on clicking any item from the list, item detail should be displayed (from ItemDetail component). problem is only url is getting changed to /detail/id, but component is not getting changed ie not changing to ItemDetail.js
Related
I am struggling to render my routes list with React router v6.
I have already read the docs and I am aware of a new structure of routing, but currently having a problem rendering list (nested) of routes.
export default [{
path: '/',
element: Root,
routes: [{
path: REGISTER,
element: Register,
isProtected: true,
}],
}];
export const RouteMatcher = ({ routes }) => {
return routes.map((route, index) => {
if (route.element) {
return (
<Route key={index} path={route.path} element={<route.element />}>
{route.routes ? (
<Route path={route.path} element={<RouteMatcher routes={route.routes} />
) : null}
</Route>
);
}
return null;
});
};
<BrowserRouter>
<Routes>
<Route path="/" element={<RouteMatcher routes={routes} />} />
</Routes>
</BrowserRouter>
It's not obvious to me what's going on here even though the error message has a clear explanation of the issue.
Even I try this way it doesn't work.
<BrowserRouter>
<Routes>
<Route path="/">
{routes.map((route, index) => {
if (route.element) {
return (
<Route key={index} path={route.path} element={<route.element />}>
{route.routes ? (
<Route path={route.path} element={<RouteMatcher routes={route.routes} indexPathname={indexPathname} />} />
) : null}
</Route>
);
}
return null;
})}
</Route>
</Routes>
</BrowserRouter>
As you can see Route is always a child of Routes or Route (when nested).
** UPDATED **
Here is the react router v5 implementation
<Provider store={store}>
<GlobalStyles />
<AppContentWrapper>
<RouteHandler />
</AppContentWrapper>
</Provider>
Route handler component
<BrowserRouter>
{generateRouteMatches(
routes,
indexPathname,
auth.isLoading,
auth.isLoggedIn
)}
</BrowserRouter>
Route generator component
export const generateRouteMatches = (
baseRoutes: IAppRoute[],
indexPathname: string,
userPermissions: PermissionShape[],
isLoading: boolean,
isLoggedIn: boolean,
) => {
AccessControl.createPermissions(userPermissions);
return baseRoutes.map((route) => (
<MatchRoutes
indexPathname={indexPathname}
authIsLoading={isLoading}
isLoggedIn={isLoggedIn}
key={route.path}
{...route}
/>
));
};
MatchRoutes component with RouteRenderer
function MatchRoutes({ location, ...route }: any) {
const routePermissions = AccessControl.getPermissions(route.zone);
if (!routePermissions.canRead && route.isProtectedRoute) {
return <Route {...omit(route, ['component'])} component={() => {
return <div className="centerXY">You dont have permissions to view this page</div>;
}} />;
}
return (
<Route {...omit(route, ['component'])} render={(props) => (
<RouteRenderer {...props} route={route} location={location} />
)} />
);
}
function RouteRenderer({ route, ...props }: any) {
const location = useLocation();
if (location?.pathname === '/') {
return (
<Redirect
to={{
pathname: route.indexPathname,
state: { from: location },
}}
/>
);
}
if (route.isProtectedRoute && !route.isLoggedIn && !route.authIsLoading) {
return (
<Redirect to={{
pathname: '/login',
state: { from: location },
}}/>
);
}
if (route.component) {
return (
<route.component
{...props}
params={props.match.params}
routes={route.routes}
>
{route.routes
? route.routes.map((cRoute, idx) => (
<MatchRoutes
authIsLoading={route.authIsLoading}
isLoggedIn={route.isLoggedIn}
key={idx}
{...cRoute}
/>
))
: null
}
</route.component>
);
} else if (route.routes) {
return (
<>
{route.routes.map((cRoute, idx) => (
<MatchRoutes
authIsLoading={route.authIsLoading}
isLoggedIn={route.isLoggedIn}
key={idx}
{...cRoute}
/>
))}
</>
);
} else {
return null;
}
}
export default MatchRoutes;
In the first example the RouteMatcher component is rendering a Route component directly. The mapped routes it is rendering need to be wrapped in a Routes component.
export const RouteMatcher = ({ routes }) => {
return (
<Routes>
{routes
.filter(route => route.element)
.map((route, index) => {
return (
<Route
key={index}
path={route.path}
element={<route.element />}
>
{route.routes && (
<Route
path={route.path}
element={<RouteMatcher routes={route.routes} />}
/>
)}
</Route>
);
})
}
</Routes>
);
};
I suspect something similar is occurring int he second code example as well.
I suggest using a better formed routes configuration and use the useRoutes hook.
Example:
export default [{
path: '/',
element: <Root />,
children: [
{
element: <AuthOutlet />,
children: [
{
path: REGISTER,
element: <Register />,
},
... other routes to protect ...
],
... other unprotected routes ...
},
],
}];
...
import appRoutes from '../path/to/routes';
...
const routes = useRoutes(appRoutes);
...
<BrowserRouter>
{routes}
</BrowserRouter>
Currently using react-router-dom 6.1.1 and I'm working with a private route.
Inside this private route I usually had other routes (so that I can keep my Sidebar on them).
My code looks like this
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/"
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
/>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route path={link} key={index}>
{component ? component : <p>[{breadCrumbtitle}] To be done</p>}
</Route>
))}
</Routes>
</div>
)
}
So... This setup worked with v5 but it seems to be something that doesn't really work with v6.
What can I do if I still want to keep the Sidebar for all the routes once I'm logged in?
I ended up finding the solution to my issue.
Doing what Drew Reese suggested only worked to a certain point since I was being led to a route that, for react router, didn't exist.
For it to work I add to do
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path=""
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
>
{routes.map(({ breadCrumbtitle, link, component }, index) => {
return <Route path={link} key={index} element={component}></Route>
})}
</Route>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<div className={css.contentContainer}>
<Outlet />
</div>
</div>
)
}
Using the Outlet seemed to be essential, don't know if it's something new on react router v6 but seemed to do the trick!
As far as I can tell the only issue is with the routes mapping, the Route components have invalid children, i.e. you are rendering another Route or React.Fragment as a child.
Move this up to the element prop of the mapped Route components.
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route
path={link}
key={index}
element={component || <p>[{breadCrumbtitle}] To be done</p>}
/>
))}
</Routes>
</div>
);
};
I'm building a movie app that pulls data from themoviedb API. Right now, on the home page, I have a search form where I type the movie I want to look for. Then below, I display the result of the movies returned with a button that takes you to a page that displays more info about a particular movie.
My issue is with the routing. How do I do it in such a way that when I'm on the movie-details page, the search and results components are not displayed
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import Navbar from "./navbar";
import Search from "./search";
import Result from "./result";
import Details from "./details";
import "./App.css";
import axios from "axios";
class App extends Component {
state = {
searchTerm: "",
movies: []
};
onSearch = e => {
e.preventDefault();
this.setState({ searchTerm: e.target.value });
};
handleSubmit = async e => {
e.preventDefault();
const result = await axios.get(
`https://api.themoviedb.org/3/search/movie?query=${this.state.searchTerm}&page=1&api_key=6f7ad5c4744feea1ee5508d2e56232c4`
);
this.setState({movies: result.data.results})
console.log(result.data.results);
};
render() {
return (
<div className="container">
<Navbar />
<Switch>
{/* <Search handleSearch={this.onSearch} /> */}
{/* <Route exact path="/" component={Search} handleSearch={this.onSearch} handleSubmit={this.handleSubmit}/> */}
= <Route
exact
path="/"
render={props => (
<Search
{...props}
handleSearch={this.onSearch}
handleSubmit={this.handleSubmit}
/>
)}
/>
<Route path="/movie/" component={Details} />
<Route path="/" component={Result} />
</Switch>
<Result movies={this.state.movies}/>
</div>
);
}
}
export default App;
https://codesandbox.io/s/github/peoray/movies-info
Your <Result /> component is outside of the router, so it's always being displayed. A quick fix here is to move it inside the router, like so:
return (
<div className="container">
<Navbar />
<Switch>
{/* <Search handleSearch={this.onSearch} /> */}
{/* <Route exact path="/" component={Search} handleSearch={this.onSearch} handleSubmit={this.handleSubmit}/> */}
<Route
exact
path="/"
render={props => (
<>
<Search
{...props}
handleSearch={this.onSearch}
handleSubmit={this.handleSubmit}
/>
<Result movies={this.state.movies} />
</>
)}
/>
<Route path="/movie/" component={MovieDetails} />
</Switch>
</div>
);
https://codesandbox.io/s/todo-app-r38v5
I'm not sure if I'm using a HOC correctly but I have state at the top app level which needs to be updated from a child components that exists in a HOC.
my main app router that holds the state
class AppRouter extends React.Component {
state = {
selected: "",
};
updateSelected = selected => {
this.setState({ selected });
};
updateReports = reports => {
this.setState({ reports });
};
render() {
return (
<Router history={history}>
<div>
<div className="holygrail">
<Header setIsAuth={this.setIsAuth} isAuth={this.state.isAuth} />
<Switch>
<PublicRoute
path="/login"
isAuth={this.state.isAuth}
component={() => <Login setIsAuth={this.setIsAuth} />}
exact={true}
/>
<PrivateRoute
path="/"
selected={this.state.selected}
isAuth={this.state.isAuth}
updateSelected={this.updateSelected}
updateReports={this.updateReports}
component={Dashboard}
exact={true}
/>
<Route component={NotFound} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default AppRouter;
I have a PrivateRoute that then has a template which include a nav that should not be shown on a PublicRoute
export const PrivateRoute = ({ component: Component, isAuth, ...rest }) => (
<Route
{...rest}
render={props => {
console.log("Private Route ", isAuth);
return isAuth ? (
<div className="holygrail-body">
<Nav
updateSelected={this.updateSelected} <-- how to pass these back up to AppRouter parent?
updateReports={this.updateReports}
/>
<Component {...props} />
</div>
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}}
/>
);
export default PrivateRoute;
How do I pass the Nav props to update the main component state.
I tried to pass in props to this.props.children using the following code
export default class Home extends Component {
render(){
var children = React.Children.map(this.props.children, function (child) {
return React.cloneElement(child, {
foo: "1"
})
});
return(
<div className="bla">
<h1>WeDate</h1>
<div className="child">
{children}
</div>)
}
}
But I can't read this.props.foo in my searchDate component when it renders normally.
The following is my react router.
render(
<Router>
<Home>
<Switch>
<Route exact path="/"><Redirect to="/search" push/></Route>
<Route exact path="/search" component={SearchDate}></Route>
</Switch>
</Home>
</Router>
,document.getElementById('app')
);
the children to your Home component are not the Routes but the Switch and hence foo is not passed down as props to the respective components. What you need to do is to nest your Routes is the Home component and not as children
Home
export default class Home extends Component {
render(){
return(
<div className="bla">
<h1>WeDate</h1>
<div className="child">
<Switch>
<Redirect from="/" exact to="/search"/>
<Route exact path="/search" render={(props) => <SearchDate foo={'1'} {...props}/>}>
</Switch>
</div>)
}
}
Routes
render(
<Router>
<Home />
</Router>
,document.getElementById('app')
);