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

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.

Related

Maintain context values between routes in React.js

I'm trying to maintain state values between routes in context. But it gets reset when the route changes.
aackage.json:
"react-router-dom": "^6.8.0",
"react-dom": "^18.2.0",
"react": "^18.2.0",
App.js:
export default const App = () => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
const getData = async () => {
setLoading(true);
const data = await axios.get("url", {
withCredentials: true,
});
setData(data);
setLoading(false);
};
useEffect(() => {
getData()
console.log("I run on route change");
}, []);
const GlobalContextValue= {
data: data,
loading: loading,
};
return (
<>
<GlobalContextProvider value={GlobalContextValue}>
<BrowserRouter>
<Routes>
<Route index element={<HomePage />} />
<Route path="/:slug" element={<PostPage />} />
{/* <Route path="*" element={<NoPage />} /> */}
</Routes>
</BrowserRouter>
</<GlobalContextProvider />
</>
)
}
Whenever I try to access any route the getData function inside the useEffect calls which inturns resets the data. I have attached a CodeSandbox to replicate the same
I don't know if this problem is related to reactJs or react-router. Thanks in advance
As you don't seem to have any navigation link, I assume you are using the browser search bar, or a normal HTML <a> tag. Well, doing so refreshes the page, so the entire app gets re-created.
Using useNavigate or Link from React Router Dom, doesn't refresh the page, hence your context data remains untouched:
const HomePage = () => {
return (
<>
<h1>Hii Homepage </h1>
<Link to="/1">Go to PostPage</Link>
</>
);
};
const PostPage = () => {
const params = useParams();
return (
<>
<h1>Hii PostPage {params.slug} </h1>
<Link to="/">Go to HomePage</Link>
</>
);
};
export default function App() {
useEffect(() => {
console.log(
"I run on load and route change with browser search bar, not with useNavigate or Link"
);
}, []);
return (
<>
{/* This context wrapping BrowserRouter keeps its value if you navigate with Link or
useNavigate. */}
<GlobalContextProvider value={{ key: "some value" }}>
<BrowserRouter>
<Routes>
<Route index element={<HomePage />} />
<Route path="/:slug" element={<PostPage />} />
{/* <Route path="*" element={<NoPage />} /> */}
</Routes>
</BrowserRouter>
</GlobalContextProvider>
</>
);
}

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.

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

React context api lose auth data when react router dom push page

I have an context where i save the user data, and i have another component when verify the context user is null, if the context user is null my component should redirect the user to the login page, if not should render the component. My routers is inside my Authprovider, but still losing the user data when reload the router. I found another posts with the same issue, and the instruction is to keep the routers inside the useauthprovider, but doesn't work with my app.
My code
function App() {
let header = window.location.pathname === '/login' || '/cadastro' ? <Header /> : null;
let footer = window.location.pathname === '/login' || '/cadastro' ? <Footer /> : null;
return (
<UseAuthProvider> // My use AuthProvider
<Router>
<div className='app-container' >
<Switch>
<Cart>
<Header />
<NavbarMenu />
<div className='app-body'>
<UseCampanhaProvider>
<PublicRoute exact path='/' component={Home} />
<PrivateRoute exact path='/cupom/:campaignId' component={CupomScreen} />
<PrivateRoute exact path='/carrinho' component={CartScreen} />
</UseCampanhaProvider>
<PublicRoute exact path='/login' restricted={true} component={Login} />
<PublicRoute path='/cadastro' restricted={true} component={Cadastro} />
</div>
<AuthModal />
{footer}
</Cart>
</Switch>
</div>
</Router >
</UseAuthProvider>
);
}
export default App;
My component where i verify the user context
const PrivateRoute = ({ component: Component, ...rest }) => {
const { user } = useAuth();
return (
<Route {...rest} render={props => (
!user ?
<Redirect to='/login' />
:
<Component {...props} />
)} />
);
};
export default PrivateRoute;
My context where i load the user
const UseAuthProvider = ({ children }) => {
const [user, setUser] = useState();
const [open, setOpen] = useState(false)
useEffect(() => {
verifyUser(); //here i call the function when verify the localstorage
}, [])
const verifyUser = async () => {
let tokenHeader = authHeader();
if (tokenHeader) {
await Api.post('/cliente/index', {}, {
headers: {
...tokenHeader
}
}).then((response) => {
setUser(response.data.cliente)
})
}
}
const handleModal = () => {
setOpen((state) => !state)
}
const Logout = async () => {
localStorage.clear('acessToken-bolao')
setUser(null)
}
return (
<useAuthContext.Provider value={{ Auth, verifyUser, user, Register, Logout, open, handleModal }}>
{children}
</useAuthContext.Provider>
)
}
I tried to debug my application and when i redirect my user to another router, before the component render my user return undefined, and after my component is rendered the context load the user data.
It sounds like your entire application is unmounting and remounting.
In this case the state will be lost as it is not simply a re-render.
By what mechanism are you navigating to the new page?
If I remember React-Router correctly you need to use
If you try navigating the url itself with window.location or href then you are reloading the entire page (not using the router in the SPA)
If routed correctly I would expect that only data inside the Switch would be re-loaded.

Can't pass data from one page to another with React-Router-DOM (useHistory, useLocation)

I have a router:
<Switch>
<Route exact path="/">
<CustomPaddingContainer padding="0 0.5em 1.5em">
<TableViewComponent columns={tableAccessors} />
</CustomPaddingContainer>
</Route>
<Route path="/new-objective">
<AddNewObjectiveComponent onSubmit={onSubmitObjective} onCancel={onCancel} />
</Route>
<Route path="/new-kr">
<AddNewKrComponent onSubmit={onSubmitKR} onCancel={onCancel} />
</Route>
<Route path="/okr-details/:id">
<OkrDetailsWithParams />
</Route>
</Switch>
and I want to pass specific data from specific component to one of this Route when specific button will be clicked. to be more precise, I have this component:
const AddOKRButtons: FC<AddOKRButtonsProps> = ({ parentObjectiveId }) => {
const history = useHistory();
const onAddOkrButtonClick = () => {
history.push('/new-objective', { parentObjectiveId: parentObjectiveId });
};
const onAddKrButtonClick = () => {
history.push('/new-kr', { parentObjectiveId: parentObjectiveId });
};
return (
<OkrDetailsChildrenCardsButtonContainerCentered>
<ButtonGroup>
<LinkButton to="/new-objective" appearance="default" onClick={onAddOkrButtonClick}>
Add a new sub-objective
</LinkButton>
<LinkButton to="/new-kr" appearance="default" onClick={onAddKrButtonClick}>
Add a new key-result
</LinkButton>
</ButtonGroup>
</OkrDetailsChildrenCardsButtonContainerCentered>
);
};
Im trying to pass the **parentObjectiveId** which is coming from props to the /new-objective page or /new-kr page in order what button was clicked. After that Im trying to get that data in component where it should be with useLocation hook:
export const AddNewObjectiveComponent: FC<NonNullable<AddNewOKRProps>> = props => {
const location = useLocation();
console.log(location);
return(<div></div>)
}
and unfortunately i got undefined in the state key, where the data is probably should be:
Try to push history route like
history.push({
pathname: '/new-objective',
state: { parentObjectiveId: parentObjectiveId }
});
I hope it will be work for you. Thanks!

Categories

Resources