ReactJS router v4 Mobx - Router wont re-render components? - javascript

I use mobx Provider to provide stores, by wrapping the Router
<Provider {...stores}>
<BrowserRouter >
<App />
</BrowserRouter>
</Provider>
In App I have two components, Header and Main.
Header contains Link to the routes and Main contains the routes Switch:
<div className="main">
<Route exact path='/' component={Home} />
<Route exact path='/login' component={Login} />
{/* <UnvalidatedUserRoute exact path='/login' store={this.props.User} component={Login} /> */}
<ValidatedUserRoute exact path='/todos' store={this.props.User} component={UserTodos} />
</div>
ValidatedUserRoute is just a function that makes sure the user is authenticated, if not, redirect to home.
const ValidatedUserRoute = ({component: Component}, store, ...rest) => (
<Route
{...rest}
render={props =>
store.validated ? (
<Component {...props} />) : (
<Redirect to={{
pathname: "/"
}}
/>
)
} />
);
export default ValidatedUserRoute
The problem
When I click on the links, my URL does change, but the components does not render, like the page is stuck on what component was loaded at first when loading the web-page.
<Link to="/">Home</Link>
<Link to='/login'>Log In</Link>

if you are using the new react-router-dom. then you have to use a withRouter Hoc given by the library and wrap your component with it.
import {withRouter} from 'react-router-dom'
const ValidatedUserRoute = ({component: Component}, store, ...rest) => (
<Route
{...rest}
render={props =>
store.validated ? (
<Component {...props} />) : (
<Redirect to={{
pathname: "/"
}}
/>
)
} />
);
export default withRouter(ValidatedUserRoute)
what ever component is responsible for performing routing. wrap that in withRouter

Related

Uncaught Error: [Elements] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I was trying to integrate the strip API key but I am not able to come up with the solution in the new react-router-dom version
here is the full error
The above error occurred in the component:
at Routes (http://localhost:3000/static/js/bundle.js:81119:5)
at Router (http://localhost:3000/static/js/bundle.js:81052:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:79861:5)
at App (http://localhost:3000/main.79cc3231add2da1b35a8.hot-update.js:89:63)
at Provider (http://localhost:3000/static/js/bundle.js:74963:5)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
here is my Appjs Protected route code:
import { Elements } from "#stripe/react-stripe-js";
import { loadStripe } from "#stripe/stripe-js";
useEffect(() => {
store.dispatch(loadUser());
getStripeApiKey();
}, []);
useEffect(() => {
const stripePromise= loadStripe(stripeApiKey);
}, [stripeApiKey]);
<Route element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} />
<Route path="/me/update" element={<ProfileEdit />} />
<Route path="/password/update" element={<UpdatePassword />} />
<Route path="/login/shipping" element={<Shipping />} />
<Route path="/order/confirm" element={<ConfirmOrder />} />
{stripeApiKey && (
<Elements stripe={stripePromise}>
<Route path="/order/payment" element={<Payment />} />
</Elements>
)}
</Route>
My protected route code
const ProtectedRoute = () => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
const location = useLocation();
if (isAuthenticated == false) {
return <Navigate to="/account" state={{ from: location }} />;
}
return <Outlet />;
};
export default ProtectedRoute;
Issue
This issue is that you are rendering something other than a Route or React.Fragment inside the Routes component.
{stripeApiKey && (
<Elements stripe={stripePromise}>
<Route path="/order/payment" element={<Payment />} />
</Elements>
)}
The Elements component is neither and fails the invariant check.
Solution
Refactor this into a layout route component similar to the ProtectedRoute component.
Example:
import { Outlet, Navigate } from 'react-router-dom';
const StripeLayout = ({ stripeApiKey }) => {
return stripeApiKey
? <Outlet />
: <Navigate to="/" replace />
};
...
<Route element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} />
<Route path="/me/update" element={<ProfileEdit />} />
<Route path="/password/update" element={<UpdatePassword />} />
<Route path="/login/shipping" element={<Shipping />} />
<Route path="/order/confirm" element={<ConfirmOrder />} />
<Route element={<StripeLayout {...{ stripeApiKey }} />}>
<Route
path="/order/payment"
element={(
<Elements stripe={stripePromise}>
<Payment />
</Elements>
)}
/>
</Route>
</Route>
You are getting that error because of this line of code
{stripeApiKey && (
<Elements stripe={stripePromise}>
<Route path="/order/payment" element={<Payment />} />
</Elements>
)}
Only Route or React.Fragment are allowed to be children of the Routes component. But in your case if stripeApiKey turns out to be true , you are rendering Elements component which contradicts the new react router v6 rules
Like you asked in comments you want to wrap your payments method
<Route path="/order/payment" element={<Elements stripe={stripePromise}><Payment /></Elements} />

Refresh component on route only in React-Router V5

Is there a way to refresh a route when using react router v5.
I have a onClick function which navigates to a route and refetches the data, however that is only if I use history.push('/') I want to refresh the component even if I am just on the same route '/' for example I am currently doing:
is there a better way to navigate programatically in react-router?
currently this handleChange method is in the header component.
const handleChange = (event: any) => {
setValue(event.target.value);
if (history.location.pathname === "/") {
alert('Changing');
// need to refresh component here?
}
history.push('/');
};
routing is:
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Header links={items} locations={Locations} />
<div className="App">
<Switch>
<Route exact path="/" component={home} />
<Route path="/details/:id" render={(props: RouteComponentProps<any>) => <Detail {...props} />} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>
<ReactQueryDevtools />
</QueryClientProvider>

react router use params returns empty object

I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.

Is there a best practice way to hide component using react router?

To hide the navbar on the home component I am doing the following
const NavbarComponent = (props) => {
console.log(props);
if (props.match.path === '/') {
return null;
} else
return (
it works fine, I need to have access to the router so I can send people to locations dependant on the props object , is there a better way to do it such that I have all router logic in the same place?
this is the current state of my router
return (
<div>
<Router>
<Route component={Navbar} />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/api/:city/electronics" component={Electronics} />
<Route exact path="/api/:city/labour" component={Labour} />
<Route exact path="/api/posts/item/:id" component={ItemDetails} />
<Route exact path="/create/:city/:category" component={CreatePost} />
</Switch>
</Router>
</div>
);
thanks for your time.
I'm not sure I understand why your NavBar component is in it's own Route. Any components contained within the Router have access to the entire Router api, including Link - they do not need to be a Route to do so.
I would suggest wrapping all the Routes that include the NavBar with that component. The Routes will then be displayed as children of the Navbar component.
Here is a simplified example:
// App.js
return (
<div>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<NavBar>
<Route exact path="/electronics" component={Electronics} />
<Route exact path="/labour" component={Labour} />
</NavBar>
</Switch>
</Router>
</div>
);
//NavBar.js
return (
<>
<div>
<Link to="/electronics">Electronics</Link>
<Link to="/labour">Labour</Link>
</div>
<div>{props.children}</div>
</>
);
codesandbox

Programmatically navigate while using HashRouter

I'm using HashRouter for my routes in a react.js app. I then have a function which does the following:
this.props.history.push('/somePath');
The problem is, ever since I started using HashRouter, the page doesn't push to that path when that function gets called.
I logged this.props.history, and push was there.
How can I programmatically navigate while using HashRouter?
Note: I used withRouter
Here's the code:
import React, { Component } from 'react';
import { HashRouter, Route, Switch, Redirect, withRouter } from 'react-router-dom';
// Other imports
class App extends Component {
navigate = () => {
this.props.history.push('/route1');
console.log(this.props.history);
};
render() {
return (
<div className="App">
<Header />
<HashRouter>
<Switch>
<Route
path="/route1"
exact
render={() => (
<FirstRoute someSetting='setting1'/>
)}
/>
<Route
path="/route2"
exact
render={() => (
<SecondRoute anotherSetting='Really Cool'/>
)}
/>
<Route path="/404" component={NotFound} />
<Route exact path="/" render={() => <HomePage someSettings='Home Page' />} />
<Redirect to="/404" />
</Switch>
</HashRouter>
<BottomBar />
</div>
);
}
}
export default withRouter(App);

Categories

Resources