This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed last month.
Error
Uncaught Error: A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .
Private Router
function PrivateRoute({ children, ...rest }) {
const auth = useAuth();
return (
<Route
{...rest}
render={() => {
if(auth.user){
return children;
}
return <Navigate to='/login' />
}}
/>
);
}
App.js
return (
<div className="App">
<Router>
<Navbar />
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/login" element={<Login />} />
<Route exact path="/register" element={<Signup />} />
<Route exact path="/settings" element={<PrivateRoute><Settings /></PrivateRoute>} />
<Route element={<Page404 />} />
</Routes>
</Router>
</div>
);
I was trying to Privating the route for settings , but got error.
Can you try to replace your settings route by this :
<Route exact path="/settings" component={PrivateRoute}><Settings /></Route>
Related
I'm using React Router v6 and am creating private routes for my application.
In file Route.js, I've the code
export default function RouteWrapper({
element: Element,
isPrivate,
...rest
}) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Route {...rest} render={(props) => <Element {...props} />} />;
}
And in file index.js I've written as:
return (
<Routes>
<Route path="/" element={SignIn} />
<Route path="/register" element={SignUp} />
<Route path="/dashboard" element={Dashboard} isPrivate />
<Route path="/profile" element={Profile} isPrivate />
<Route path="/customers" element={Customers} isPrivate />
<Route path="/new" element={New} isPrivate />
<Route path="/new/:id" element={New} isPrivate />
</Routes>
);
}
Is there something I'm missing?
Issue
RouteWrapper isn't a Route component, and fails an invariant check by react-router-dom.
RouteWrapper is directly rendering a Route component, which if the first invariant wasn't failed would trigger another invariant violation. Route components can only be rendered directly by the Routes component or another Route component in the case of building nested routing.
In short, in react-router-dom#6 custom route components are no longer supported. You should instead use wrapper components/layout routes to handle this use case.
Solution
Convert RouteWrapper to a wrapper component that renders an Outlet component for nested routed components to be rendered into.
Example:
import { Navigate, Outlet } from 'react-router-dom';
export default function RouteWrapper({ isPrivate }) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Outlet />; // <-- nested routes render here
}
Wrap the routes you want to protect with the RouteWrapper.
return (
<Routes>
<Route element={<RouteWrapper />}>
<Route path="/" element={<SignIn />} />
<Route path="/register" element={<SignUp />} />
</Route>
<Route element={<RouteWrapper isPrivate />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/customers" element={<Customers />} />
<Route path="/new" element={<New />} />
<Route path="/new/:id" element={<New />} />
</Route>
</Routes>
);
See Layout Routes for further details.
You should convert
<Routes>
<Route exact path="/" element={Dashboard} />
</Routes>
to
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
Also if you want to keep your UI in sync with the URL use like this.
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
</BrowserRouter>
Best.
I am making a login application when i am clicking the login button the i am reciving an error (loginPage.js:33 Uncaught (in promise) TypeError: setLoginUser is not a function)
here is the code of app.js
function App() {
const [user,setLoginUser] = useState({})
return(
<div className='App'>
<BrowserRouter>
< Routes>
<Route path="/homepage" element={user && user?._id? <Homepage /> : <Login setLoginUser={setLoginUser} />}/>
<Route path="/login" element={<Login />} setLoginUser={setLoginUser}/>
<Route path="/register" element={<Register />} />
</Routes>
</BrowserRouter>
</div>
)
}
and here is the code of loginPage.js where error is occuring
const Login = ({ setLoginUser }) => {
const login = () => {
axios.post("http://localhost:4000/login", user)
.then(res => {
alert(res.data.message)
console.log(res.data.user)
setLoginUser(res.data.user)
navigate('/homepage')
})
}
}
You have to pass your setLoginUser function to Login component in your routes.
<Route path="/login" element={<Login setLoginUser={setLoginUser} />} />
You should replace :
<Route path="/login" element={<Login />} setLoginUser={setLoginUser}/>
by
<Route path="/login" element={<Login setLoginUser={setLoginUser}/>} />
I don't think it's a good idea to path variable like that in the root element but this should works.
Why do you have <BrowserRouter> and <Routes> ?
I am trying to setup nesting in the react router. I have the following code:
import React from 'react';
import DefaultSwitch from './components/DefaultSwitch/DefaultSwitch';
import './scss/App.scss';
const App = () => {
return (
<DefaultSwitch />
);
};
export default App;
With DefaultSwitch defined as:
const DefaultSwitch = () => {
return (
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
);
}
Inside the Dashboard I have the following:
const Dashboard = () => {
return (
<div>
<MyNavbar />
<DashboardSwitch />
</div>
);
};
And finally DashboardSwitch as:
const DashboardSwitch = () => {
return (
<Switch>
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
);
}
Routing appears to work and the correct components are loaded, however I have noticed that if for example I am at /dashboard and then navigate to /dashboard/bonuses the entire page is reloading including the MyNavbar component. I want the navbar to remain static and only the content below it to reload as I have defined in the Dashboard component.
What am I doing wrong here?
Consider using a layout common to all components or something like this to avoid lose MyNavbar, for example:
const App = () => (
<BrowserRouter>
<Layout>
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
</Layout>
</BrowserRouter>
)
const Layout = ({ children }) => (
<div>
{children}
<MyNavbar />
</div>
);
I have the following React component:
function Routes(props) {
const { role, userId } = props;
const renderMemberTasksPage = (props) => {
//Redirects to 404 or not
};
const renderTracksPage = (props) => {
//Redirects to 404 or not
};
return (
<>
<Switch>
<Route exact path='/members/:id/tasks' render={renderMemberTasksPage} />
<Route exact path='/members/:id/tasks/id:open?' render={renderMemberTasksPage} />
{role === 'admin' && (
<Route path='/members/new'>
<NewMember />
</Route>
)}
{(role === 'admin' || role === 'mentor') && (
<>
<Route exact path='/'>
<Redirect to='/members' />
</Route>
<Route exact path='/members'>
<MembersManagerPage />
</Route>
<Route exact path='/tasks/'>
<MemberTasksPage />
</Route>
<Route path='/tasks/new'>
<NewTask />
</Route>
<Route exact path='/tasks/id:open?'>
<MemberTasksPage />
</Route>
<Route path='/tasks/id:open/edit'>
<MemberTasksPage edit />
</Route>
<Route path='/members/:id/progress'>
<MemberProgressPage />
</Route>
</>
)}
{role === 'member' && (
<>
<Route exact path='/'>
<Redirect to={`/members/${userId}/tasks`} />
</Route>
<Route path='/members/:id/tracks' render={renderTracksPage} />
</>
)}
<Route exact path='/404'>
<Error404Page />
</Route>
<Route path='*'>
<Redirect to='/404' />
</Route>
</Switch>
</>
);
}
In simple words, this code defines routes depending on the current user role. Here I've got a problem: default router * is not working. Different order of routes and using exact in different combinations showed no results. When I removed all routes, rendered conditionally, it worked. Can it be the reason and how to avoid such behavior?
My version of react-router-dom:
"react-router-dom": "^5.1.2"
I think you can remove the path='*' and put directly the component inside the <Route>
It would be something like that:
<Route>
<Error404Page />
</Route>
since the Switch will try to match every path the last one will be used if it can't find any
I'm using React Router to display the same component but with different props on each route:
<BrowserRouter>
<div>
<Route exact path="/" render={() => LOGGED_IN ? <Redirect to="/profile" /> : <Login />} />
<Route path="/profile/:id" render={(props) => <App {...props} page="profile" pageLadder={['Home', 'Profile']} />}/>
<Route path="/team" render={() => <App page="team" pageLadder={['Home', 'Team']} />}/>
<Route path="/transactions" render={() => <App page="transactions" pageLadder={['Home', 'Transactions']} />}/>
<Route path="/tournaments" render={() => <App page="tournaments" pageLadder={['Home', 'Tournaments']} />}/>
<Route path="/tournament/:id" render={(props) => <App {...props} page="tournament" pageLadder={['Home', 'Tournament', props.match.params.id]} />}/>
<Route path="/match/:id" render={(props) => <App {...props} page="match" pageLadder={['Home', 'Match', props.match.params.id]} />} />
<Route path="/scrims" render={() => <App page="scrims" pageLadder={['Home', 'Scrims']} />} />
<Route path="/faq" render={() => <App page="faq" pageLadder={['Home', 'FAQ']} />} />
<Route path="/staff" render={() => <App page="staff" pageLadder={['Home', 'Staff']} />} />
<Route path="/privacy" render={() => <App page="privacy" pageLadder={['Home', 'Privacy Policy']} />} />
<Route path="/tos" render={() => <App page="tos" pageLadder={['Home', 'Terms of Service']} />} />
</div>
</BrowserRouter>
I also need to catch 404 errors, so I added Switch and <Route component={NotFound} />:
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => LOGGED_IN ? <Redirect to="/profile" /> : <Login />} />
... more routes
<Route component={NotFound} />
</Switch>
</BrowserRouter>
The 404 does work, but every <Link> element kind of stops working - after clicked, the url does change but the component stays the same, unless I refresh the site.
I tried changing my code to this to test:
<BrowserRouter>
<Switch>
<Route path="/team" component={Login} />
<Route path="/profile/" component={App} />
</Switch>
</BrowserRouter>
With this code the <Link> component does work as intended.
What can I do to make my first code work as intended?
From your code example:
class App extends Component {
constructor(props){
super(props);
// appStore is an instance of react-easy-state library
// App component puts the components together, the actual pages are in AppMain component (look below for code)
appStore.changePage(this.props.page, this.props.pageLadder, this.props.match ? this.props.match.params.id : -1);
// this function changes appStore.page.name value and it's a global type store
}
render() {
return (
<div>
<div>
<Sidebar />
<AppMain />
</div>
</div>
);
}
}
You are calling appStore.changePage in the constructor, which only get's called one time, when the component is first initialized. Even though you have the component on several routes, it's not getting unmounted when you change the route and props, meaning that the constructor is never getting called again.
What you need to do is use componentDidUpdate to change the page when your props change:
componentDidUpdate (prevProps, prevState) {
if(prevProps.page !== this.props.page) {
appStore.changePage(this.props.page, this.props.pageLadder, this.props.match ? this.props.match.params.id : -1);
}
}
Now, you compare the prevProps from the current props and if the page prop has changed, you need to trigger appStore.changePage again. Now, I'm not sure what appStore.changePage is doing so I'm not sure if you'll need to update <AppMain /> as well, but this should get you on the right track.