Map Components not rerendered after navigate back - javascript

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.

Related

react-dom-router Link component doesnt work as expected

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])
...

Rendering two different components with Reactjs

I've 2 different components, and I would like to render them separately. Logic is simple when user is logged in render 1st component, if it's not render default component.
Please review my code, and help me with this, I'm not good at react, so please help me
export default function App(props) {
const Authenticated = props.Authenticated
const RoutingCabinet = (
<React.Fragment>
<Header/>
<Switch>
<Route exact path = '/' component = { () => TempBody} />
<Route exact path = '/new' component = { () => <ResponsiveDrawer />}/>
</Switch>
<Footer/>
</React.Fragment>
);
const RoutingContent = (
<React.Fragment>
<Cabinet />
<Switch>
<Route exact path = '/user.test'component = { () => <div>Element</div> }/>
</Switch>
</React.Fragment>
);
return(
<ThemeProvider theme = {Theme}>
<BrowserRouter>
{
Authenticated
? RoutingCabinet
: RoutingContent
}
</BrowserRouter>
</ThemeProvider>
);
}
Currently I can see only RoutingCabinet
Here is the index.js
ReactDOM.render(<App Authenticated = {false} />, document.getElementById('__body__', '__root__'));
I may be wrong here, but is <Cabinet /> the same as <RoutingCabinet />? If so, it might work ok. The <div>Element</div> will be visible only if url matches user.test. If you are on any other page, this won't be rendered, so it'll look like <RoutingCabinet /> is rendered, while in fact it's <RoutingContent />, it just renders the same output.

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! :)

Organizing React routes into separate components

I'm trying to find a way to organize my routes to assist the dev who might be taking over my work in the future. I thought of separating my <Route /> entries into separate components and then just load those into a main component similar to how users are assigned groups.
The issue is that when using more than one component only the first one works. This might not be the most react way of doing this so I'm also open to alternatives.
Original route arrangement
const AllRoutes = () => {
return (
<Switch>
{/* public routes*/}
<Route path={'/about'} component={AboutView} />
<Route path={'/project'} component={ProjectView} />
<Route path={'/contact'} component={ContactView} />
{/* auth routes */}
<Route path={'/login'} component={LoginView} />
<Route path={'/logout'} component={LogoutView} />
<Route component={Error404View} />
</Switch>
)
}
Separating the public routes from the auth ones:
const PublicRouteGroup = () => {
return (
<>
<Route path={'/about'} component={AboutView} />
<Route path={'/project'} component={ProjectView} />
<Route path={'/contact'} component={ContactView} />
</>
)
}
const AuthRouteGroup = () => {
return (
<>
<Route path={'/login'} component={LoginView} />
<Route path={'/logout'} component={LogoutView} />
</>
)
}
This way I can use it as such:
const AllRoutes = () => {
return (
<Switch>
<PublicRouteGroup /> {/* This works */}
<AuthRouteGroup /> {/* This doesn't */}
{/* This 404 is not a route group */}
<Route component={Error404View} />
</Switch>
)
}
Flipping <PublicRouteGroup /> and <AuthRouteGroup /> only changes the order:
const AllRoutes = () => {
return (
<Switch>
<AuthRouteGroup /> {/* This works */}
<PublicRouteGroup /> {/* This doesn't */}
{/* This 404 is not a route group */}
<Route component={Error404View} />
</Switch>
)
}
Update #1
This is thanks to #skyboyer. By moving the <Switch> to the child components and removing it from the AllRoutes component each component started to show. It appears adding the <Switch> in AllRoutes is allowing only the first hit to show which is as <Switch> does. But now by removing it it shows the 404 at the end of each page as well.
Basically, it looks like this:
const AllRoutes = () => {
return (
<>
<Route component={AuthRouteGroup} /> {/* This works */}
<Route component={PublicRouteGroup} /> {/* This also works */}
{/* This 404 is not a route group */}
<Route component={Error404View} /> {/* Always shown at the bottom */}
{/* Even putting the 404 in its own RouteGroup yields the same issue */}
</>
)
}
It appears this current set up of treating components like OOP classes you can extend from is the wrong approach. I've instead made use of arrays since these can be acted upon by the spread operator. It still accomplishes the same goal of organizing routes across an infinite number of groups which was what I was after.
Create the array for each group
const public_route_group = [
{path: '/about', component: AboutView},
{path: '/project', component: ProjectView},
{path: '/contact', component: ContactView},
]
const auth_route_group = [
{path: '/login', component: LoginView},
{path: '/logout', component: LogoutView},
]
const error_route_group = [
{component: Error404View} // No path required
]
const user_routes = [
...public_route_group,
...auth_route_group,
...error_route_group
]
Create the routes
const AllRoutes = () => {
return (
<Switch>
{user_routes.map((route, idx) => {
return <Route key={idx} {...route} />
})}
</Switch>
)
}
I figure this can also be modified further if you're using nested objects in your array.
I'd like to thank #skyboyer for providing an insight into this problem.
How about having it without Swtich at top-level
<Route component={PublicRouteGroup} />
<Route component={AuthRouteGroup} />
so they are rendered unconditionally. And then having extra Switch in your components like
const AuthRouteGroup = () => {
return (
<Switch>
<Route path={'/login'} component={LoginView} />
<Route path={'/logout'} component={LogoutView} />
<Switch/>
)
}
But why id did not work?
The reason is how Switch works:
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
See, even if AuthRouteGroup is not a Route, Switch anyway looks to its props.path. And once undefined for props.path matches any path and Switch renders only first matching Route you are getting only first component rendered.
[UPD] "does-not-match-any-route" View will work only at top level of Switch. Also there are no way to know if some nested children of sibling element has matched current route or not. So only way I see is listing all routes in single place.
Alternative that looks rather poor is having special route "/error404" and redirect user to it from inside of other components(but who should decide? and where? and when?).

Categories

Resources