Fetching data using React Router and useLocation - reactjs

I want to trigger an api call each time a route is visited in react router. For example each time I route to /list The List component will call its API. It’s not possible to do this with useEffect when the component mounts because it’s not mounted each time the route is triggered. I am considering watching the location hook like so:
import { useLocation } from 'react-router-dom';
const MyComponent = () => {
const location = useLocation()
React.useEffect(() => {
// runs on location, i.e. route, change
console.log('handle route change here', location)
}, [location])
...
}
My issue is that if several components are watching the location then they will all have their useEffect triggered and call their APIs. I need to verify that this location is in fact the one that is currently displayed on the screen. I could use location.pathName to get the path but how do I verify this is the correct path for the component? Or am I going about this the wrong way?

Related

React & Socket.io: How to detect when user redirects to another route before actually redirecting

Socket.io beginner here. I'm building a practice chat app using react and socket.io and was wondering how to detect and emit an event when someone clicks on another route before them actually being redirected. I would use this to remove them from the online list in that room but not remove everywhere like the built in disconnect. I have searched other questions and tried the code below but it only gets called on the initial mount
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function SomeComponent() {
const location = useLocation();
useEffect(() => {
console.log('Location changed');
}, [location]);
...
}
I'm kind of looking for something like
useEffect(() => {
if(location.path !== currentPath) {
socket.emit('remove_user', {})
}
}, [something])
You might need to put that useEffect on the root component, the App.js for example, if you don't already. Try it.
In the example I put it on the App and it work fin, but when I put it the PostMain component it only work when i navigate to /post

How to detect if context provider is present in a React app?

Context
I'm working with a hybrid next.js and react-router app. Parts of the app are handled by react-router (hash-based), and parts of it by next.js router. There are common components which use hooks related to the current routing state (e.g. useLocation), which crash if the react-router provider wrapper is missing.
Problem
I would like to write a hook that returns either useLocation (from react-router-dom) or useRouter (from next.js), depending on whether it detect the react-router provider in the current context.
Then I would use this hook in common components, so that they work regardless of which context they're used in.
There is a similar solution for detecting whether to use useEffect or useLayoutEffect for SSR, called useIsomorphicLayoutEffect. I'm thinking that a similar approach could work in my case. However, feel free to suggest different solutions.
The error I'm getting is TypeError: useContext(...) is undefined. The react-router wrapper provides a context which is used by the useLocation hook. Therefore I believe a generic solution for detecting the context provider would be valid here.
Example
const fooCommonComponent = () => {
// ❌ this only works when react-router-dom provider exists in the current context
const { pathname } = useLocation();
// ❌ this only works for next.js router
const { pathname } = useRouter();
// ✅ what i want
const { pathname } = useCustomLocation();
};
const useCustomLocation = () => {
// how to implement this?
};
I'm not very familiar with the Next.js side of things, but react-router-dom#6 has an useInRouterContext hook to return true/false if the component is rendered within a RRD routing context.
useInRouterContext
The useInRouterContext hooks returns true if the component is
being rendered in the context of a <Router>, false otherwise. This
can be useful for some 3rd-party extensions that need to know if they
are being rendered in the context of a React Router app.
Here's an example implementation that works for at least the RRD side of things.
import { useInRouterContext, useLocation } from "react-router-dom";
import { useRouter } from "next/router";
const useCustomLocation = () => {
const isInRRDContext = useInRouterContext();
return (isInRRDContext ? useLocation : useRouter)() ?? {};
};

During state change in React, is it possible to append values to URL

I was wondering, if, on state change, I can add something like this to URL
www.abc.com/?state=on
without page reloading.
You can use History.replaceState() in useEffect to change url when state changing
useEffect(() => {
window.history.replaceState(
history.state,
null,
`?state=${state}`
);
}, [state]);
You could use the useNavigate hook to navigate to any path you'd want to (This is available from react-router-v6 onwards, if you are using an older version, consider using useHistory). This doesn't cause any page reloads, further using useParams you could also extract the param values to make necessary computations in the target component.
import {useNavigate} from 'react-router-dom'
//OR
import {useHistory} from 'react-router-dom' //Prior to react-router-v6
export default function Component(props){
const navigate = useNavigate()
//OR
const history = useHistory()
useEffect(() => {
history.push({pathname:'/',
search:'?state=on'
})
//OR
navigate({
pathname:'/',
search:'?state=on'},{replace:false}) //Set replace=true if you'd want to disable back navigation, false otherwise.
}, [state]);
//REST OF THE COMPONENT LOGIC
.
.
.
}
I suggest you check out the docs of react router for more information.
history.push({
pathname: '/',
search: '?state=on'
})
You can use history.push and don't worry about your page reload.
https://v5.reactrouter.com/web/api/history

Keep redux state on route change

I have react-redux and react-router in my webapp and im trying to change the route while keeping the redux state. I tried all of these and they all removed the redux state:
props.history.push({ pathname: `/my-path`}); // did this using withRouter and useHistory
<Link to={'/my-path'} />cool link</Link>
What am I doing wrong and how can I keep the state? (The reason I can't keep it in localStorage is because when the user closes that page, then the data should go away)
Redux doesn't preserve the state once you refresh the website. If you want to persist data you should use localStorage, and use the event window.onunload to clean it once your browser or page is closed.
Check out this gist
Alternatively, you can pass data when you navigate programatically just like this
import { useHistory } from 'react-router-dom'
...
function myComponentA() {
const history = useHistory()
const navigate = () => {
history.push('/pageB', {
id: 7,
name: 'Dan'
color: 'Red'
})
}
return <button onClick={navigate}>Go to page B</button>
}
...
In component B, use the hook useLocation() and then access the state property, and you should see your data right there.
import { useHistory } from 'react-router-dom'
...
function myComponentB() {
const location = useLocation()
return <h1>{location.state.name}</h1>
}
...
What calls my attention is that if you are using react-router-dom, the link button should preserve the state in your redux store. The data is only cleaned once your browser reloads. Check this sample using hooks and redux-toolkit, which could be the real solution for your problem. Once you navigate to the component B the state should persist.
For more documentation see
Redux Toolkit: https://redux-toolkit.js.org/
react-router-dom: https://reactrouter.com/web/guides/quick-start

How to detect route changes with react-router v4?

I need to detect if a route change has occurred so that I can change a variable to true.
I've looked through these questions:
1. https://github.com/ReactTraining/react-router/issues/3554
2. How to listen to route changes in react router v4?
3. Detect Route Change with react-router
None of them have worked for me. Is there a clear way to call a function when a route change occurs.
One way is to use the withRouter higher-order component.
Live demo (click the hyperlinks to change routes and view the results in the displayed console)
You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
import {withRouter} from 'react-router-dom';
class App extends Component {
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change!');
}
}
render() {
return (
<div className="App">
...routes
</div>
);
}
}
export default withRouter(props => <App {...props}/>);
Another example that uses url params:
If you were changing profile routes from /profile/20 to /profile/32
And your route was defined as /profile/:userId
componentDidUpdate(prevProps) {
if (this.props.match.params.userId !== prevProps.match.params.userId) {
console.log('Route change!');
}
}
With React Hooks, it should be as simple as:
useEffect(() => {
const { pathname } = location;
console.log('New path:', pathname);
}, [location.pathname]);
By passing location.pathname in the second array argument, means you are saying to useEffect to only re-run if location.pathname changes.
Live example with code source: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i
React Router v5 now detects the route changes automatically thanks to hooks. Here's the example from the team behind it:
import { Switch, useLocation } from 'react-router'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
This example sends a "page view" to Google Analytics (ga) every time the URL changes.
When component is specified as <Route>'s component property, React Router 4 (RR4) passes to it few additional properties: match, location and history.
Then u should use componentDidUpdate lifecycle method to compare location objects before and after update (remember ES object comparison rules). Since location objects are immutable, they will never match. Even if u navigate to the same location.
componentDidUpdate(newProps) {
if (this.props.location !== newProps.location) {
this.handleNavigation();
}
}
withRouter should be used when you need to access these properties within an arbitrary component that is not specified as a component property of any Route. Make sure to wrap your app in <BrowserRouter> since it provides all the necessary API, otherwise these methods will only work in components contained within <BrowserRouter>.
There are cases when user decides to reload the page via navigation buttons instead of dedicated interface in browsers. But comparisons like this:
this.props.location.pathname !== prevProps.location.pathname
will make it impossible.
How about tracking the length of the history object in your application state? The history object provided by react-router increases in length each time a new route is traversed. See image below.
ComponentDidMount and ComponentWillUnMount check:
React use Component-Based Architecture. So, why don't we obey this rule?
You can see DEMO.
Each page must be wrapped by an HOC, this will detect changing of page automatically.
Home
import React from "react";
import { NavLink } from "react-router-dom";
import withBase from "./withBase";
const Home = () => (
<div>
<p>Welcome Home!!!</p>
<NavLink to="/login">Go to login page</NavLink>
</div>
);
export default withBase(Home);
withBase HOC
import React from "react";
export default WrappedComponent =>
class extends React.Component {
componentDidMount() {
this.props.handleChangePage();
}
render() {
return <WrappedComponent />;
}
};

Resources