react-dom-router Link component doesnt work as expected - javascript

I have a react app with two pages--- the home and trip page. The trip page is rendered with the tripID passed in through the url. My app.js looks like :
function App() {
return (<>
<ThemeProvider theme={theme}>
<Router>
<Routes>
<Route exact path='/' element={<Home />} />
<Route path='/trip/:tripId' element={<TripComponent />} />
</Routes>
</Router>
</ThemeProvider>
</>
);
}
I have a global navbar with a menu of different tripIds as many Link to navigate to the TripComponent. When i am at the "/" path, and I navigate to "/trip/tripA", i have no problems. But when i am at "/trip/tripA" , and i want to navigate to "/trip/tripB", it doesnt work. I can see the url changing accordingly, but my TripComponent doesnt rerender with tripB's data. the code for my menu in my navbar looks like:
ReactDOM.createPortal(<>
<CustomModal setOpen={setShowTripList} title={"Saved Trips"}>
<List>
{trips && trips.length > 0 &&
trips.map((trip, index) => {
return (
<Link to={`/trip/${trip._id}`} >
<ListItemButton>
<ListItemText id={trip._id} primary={trip.title} />
</ListItemButton>
</Link>
)
})
}
</List>
</CustomModal>
</>
, document.getElementById("portal"))
I am confused as to why this is happening. When i press the Link to navigate to another URL, shouldn't it unmount and remount?

When the tripId route path parameter updates the routed element TripComponent isn't remounted, it is only rerendered. If there is some logic that depends on the tripId path parameter then TripComponent needs to "listen" for changes to that value. This is the same thing that would need to happen if a prop value changed.
Use a useEffect hook with a dependency on the tripId path parameter to issue side-effects based on the current value.
Example:
import { useParams } from 'react-router-dom';
...
const TripComponent = () => {
const { tripId } = useParams();
useEffect(() => {
// initial render or tripId updated
// do something with current tripId value
}, [tripId]);
...
};

I think the #Drew's answer is perfect.
But I'd like to put something additionally.
I suggest you to use useMemo hook.
...
const trip = useMemo(() => {
// something you want
}, [tripId])
...

Related

Map Components not rerendered after navigate back

i have a page with the following code:
// Homepage
return (
<>
<div>HELLO BE YOu</div>
{list.map((video) => {
return <VideoPreview key={video._id} video={video</VideoPreview>
});}
{list.map((video) => {
return <div>TEST</div>
})}
</>
);
VideoPreview is an imported component:
export const VideoPreview = ({video}) => {
const navigate = useNavigate();
function handleClick(){
navigate('/video');
}
return <div onClick={handleClick}>video</div>
}
When a user clicks on <VideoPreview/>, they will be directed to another page which has been defined in App.js to be
<BrowserRouter forceRefresh={true}>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/video" element={<Videopage />} />
</Routes>
</BrowserRouter>
The bug is that when the user attempts to go back to "/" path from "/video" path, the HomePage component does not render properly.
The items inside the list map do not render. (Other element outside of list.map eg, <div>HELLO BE YOu</div> was rendered properly though). I have verified that list is not empty and when i place console.log statements within the map function, those logs gets printed out.
{list.map((video) => {
return <VideoPreview key={video._id} video={video}></VideoPreview>
});}
{list.map((video) => {
return <div>TEST</div>
})}
May i get some help in resolving this problem? Thank you.

How to prevent rerenders with react-router-dom and react-redux when changing route?

I have these routers
function Rutas(){
<Provider store={store}>
<Router>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
</Router>
</Provider>
}
This is my Sidebar
function Home(){
return (
<>
<NavLink to="/home">Home</NavLink>
<NavLink to="/about">About</NavLink>
</>
)
}
And this is the Home component
function Home(){
const dispatch = useDispatch();
const homeData = useSelector((data) => data.homeData);
React.useEffect(() => dispatch(getHomeDataAction),[dispatch])
return (
<>
{
homeData.map((res) => <span>{res.title}</span>)
}
</>
)
}
And this is the About component
function About(){
const dispatch = useDispatch();
const aboutData = useSelector((data) => data.aboutData);
React.useEffect(() => dispatch(getAboutDataAction),[dispatch])
return (
<>
{
aboutData.map((res) => <span>{res.title}</span>)
}
</>
)
}
On page load for the first time the Home component rendered, that's okay, when i change route to About component it's rendered too and this it's okay, but the problem it's when i change route again to the Home component it's rendered again and it call useEffect and dispatch again, I WANT TO PREVENT TO DISPATCH AGAIN WHEN THE ROUTE CHANGE BECOUSE I HAVE A LOT OF DATA AND IT TAKE A WHILE TO RENDERED AGAIN THE DATA FROM USESELECTOR AND THE UI IT'S SO SLOW.
Please tell me some solution or recommendations.
Thanks ☺
You could somehow memorize does getHomeDataAction action was dispatched or not.
const loadedData = useSelector((data) => data.homeData.loaded);
React.useEffect(() => {
if (!loadedData) {
dispatch(getHomeDataAction)
}
},[dispatch, loadedData])
All in all calling the getHomeDataAction action conditionally could help.
Apart from these changes you should extend your reducer.
After dispatching getHomeDataAction action the first time the value of the loaded property should be turned to true.

What's the best way to redirect a user to a page only if certain conditions are valid?

I'm using React for my SPA. I know how Routes work, and I know how to make a PrivateRoute.
The problem is that I need to verify my users identity before they being able to use a PrivateRoute. I tried to implement it like this:
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => {
if (isAuthenticated()) {
if (isVerified()) {
return <Component {...props} />;
} else if (isInProcess()) {
// replace url and render verification component
history.push("/inverification");
} else {
// replace url and render document upload
history.push("/documentupload");
return <DocumentUpload />;
}
} else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}
}}
/>
);
}
export default function Routes() {
// manage mobile drawer
const [drawer, setDrawer] = useState(false);
return (
<Suspense fallback={<Loader />}>
<Router history={history}>
<div className="root-div">
<Navbar setDrawer={setDrawer} />
<Drawer drawer={drawer} setDrawer={setDrawer} />
<div className="content-div">
<Switch>
<Route exact path="/" component={Main} />
<Route exact path="/login" component={Login} />
<Route
path="/login/callback"
component={Callback}
/>
<PrivateRoute path="/signup" component={Signup} />
<Route path="/logout" component={Logout} />
<PrivateRoute
path="/test"
component={() => <h1>Success</h1>}
/>
</Switch>
</div>
<Footer />
</div>
</Router>
</Suspense>
);
}
So when logging in, I save a cookie with some information about my user, encoded in a JWT token.
isVerified() and isInProcess() check if the user is already able to use the PrivateRoute. If isVerified() is true, then he can proceed to the component, else it checks if the user is in the process of being verified.
What I need to do is:
Right after the user signup, the form saves the user info and push the history to /documentupload. I need this component to render ONLY if isVerified() and isInProcess() are false.
If the user already submitted his documents and isInProcess() is true, than I need to push to /inverification and render another component, but ONLY if isInProcess() is true and isVerified() is false.
For both the above cases, isAuthenticated() must be true.
The way I implemented don't work, it renders a blank page instead of rendering the <DocumentUpload /> component.
My question is: what's the best way to achieve what I need?
I think that creating another function, like function DocumentUploadVerification similar to the PrivateRoute function is a way, but I guess it will be too verbose, and there might be a better way to solve it.
What are your thoughts?
Thanks in advance!
Your component never rerenders again. in this case you can repeat isInProcess|isVerified checks or use state manager like redux for storing current state for isInProcess|isVerified
const [_isInProcess, setIsInProcess] = useState(false);
useEffect(() => {
const i = setInterval(() => {
if (isInProcess()) {
setIsInProcess(true);
clearInterval(i);
}
}, 200);
clearInterval(i);
}, []);
When you do:
history.push("/documentupload");
return <DocumentUpload />;
you will be switched to the /documentupload route that doesn't exist in your Routes component and that is the reason you are seeing the blank page. What I would do is:
In routes add a new entry:
<PrivateRoute path="/documentupload" component={DocumentUpload} />
and remove return from PrivateRoute. This way DocumentUpload will be "protected" + history.push will save you from a recursion problem since when you try to push the location you are already on it will just ignore it(if you don't like the warning you can always add a check if you are already in that location).
Hope this helps.

Component being remounted when route change

I am experiencing the following problem:
I have two screens in my application, one if the user has access and one if not.
If the user has access to the system, he will be redirected to the screen A, a private route that has internal states, when the private routes change, the internal state of that screen A should continue until he changes to a non-private or unknown route.
The point is, I have a private routes vector, but when I loop these routes and add a key to each Router component, on each change of route, it will unmount and mount component A (Code sample here), so I lose the internal state of A, and if I add the key to the child component of A, the internal state remains as I would like (Code sample here), however I break the child key rule of react.
Warning: Each child in a list rule should have a unique" key "prop.
Any help would be amazing! :)
#Edit: the code snippet of first sandbox. The difference between the first one and the second is the key prop, instead it be inside Route, it is within the component.
#Edit 2:
I've fixed it cdeclaring all routes statically and letting the access policy come dinamically. Ty for help!
If anyone find a better solution, It'll be wellcome! :)
{ canAccess: true, path: "/home", component: () => <div>Home</div> },
{ canAccess: true, path: "/foo", component: () => <div>Foo</div> },
{ canAccess: false, path: "/blah", component: () => <div>Blah</div> }
];
const Homepage = () => {
return (
<div>
<Link to="/home">Home</Link>
<br />
<Link to="/foo">Foo</Link>
<br />
<Link to="/blah">Blah</Link>
</div>
);
};
const Main = ({ children }) => {
const [innerState, setInnerState] = useState(112);
return (
<div>
{children}
{JSON.stringify(innerState)}
<br />
<button onClick={() => setInnerState(innerState + 1)}>AddNumber</button>
<Homepage />
</div>
);
};
const PrivateRoute = ({ component: Component, path, canAccess, index }) => (
<Route
key={index}
path={path}
render={() =>
canAccess ? (
<Main>
<Component />
</Main>
) : (
<div>Not found :(</div>
)
}
/>
);
function App() {
return (
<div className="App">
<BrowserRouter>
<Switch>
{defaultRoutes.map((route, index) => {
return PrivateRoute({ index, ...route });
})}
<Route path="/" exact component={() => <Homepage />} />
<Route component={() => <div>Not found :(</div>} />
</Switch>
</BrowserRouter>
</div>
);
}
I've fixed it cdeclaring all routes statically and letting the access policy come dinamically. Ty for help!
If anyone find a better solution, It'll be wellcome! :)

Render Child Component based on route param

I have a react router implemented as such:
ReactDOM.render((
<Router history={createBrowserHistory()}>
<Route path="/" component={App}>
<IndexRoute component={IndexPage} />
<Route name="listView" path="things" component={ThingsList}>
<Route name="expandedView" path=":_id" component={ThingExpanded} />
</Route>
</Route>
</Router>
), document.getElementById("body"));
I'm rendering the list, and registering the route with no problem. with below component.
let ThingsList = React.createClass({
render() {
return (
<div>
<ul>
{things.map((thing) => {
return (
<li key={things._id}>
<Link to={`/things/${thing._id}`}>
<span>{thing.firstName}</span>
</Link>
</li>
)
}
</ul>
</div>
)
}
})
But how can I show additional data, when one clicks the link and registers the route for expanded view of a particular item? I need to read the router info, and accordingly render the additional info. I mean it should not be based on onClick, based on the route. Something like this?:
componentDidMount () {
let rName = Router.getName;
if (rName == "expandedView") {
let rSlug = Router.name("expandedView").slug("_id");
this.setState({expand: true, which: rSlug});
}
}
render() {
let extra;
if (this.state.expand) {
extra = <ThingExpanded />
}
}
and then in the List component:
render() {
return (
...
<Link to={`/things/${thing._id}`}>
<span>{thing.name}</span>
{extra}
</Link>
...)
}
Note: This answer is very close to what I need, but didn't work. :/
Conditional handler based on slug in React Router?

Categories

Resources