Problem with separating ContextProvider from app.jsx - reactjs

I just started using createContext/useContext and after successfuly implementing it I would like now to put it in seperate file. The problem is that i am getting error of too many re-renders and I don't really know whats the problem. Could you give me a clue what might be wrong? Below I have put context code,app.js and example of component where context is actually used.
***context***
import React, { createContext, useState } from "react";
export const DarkModeContext = createContext({
isDarkMode: false,
toggleIsDarkMode: () => {},
});
export const DarkModeContextProvider = ({ children }) => {
const [isDarkMode, setIsDarkMode] = useState(false);
const toggleIsDarkMode = setIsDarkMode((prev) => !prev);
const value = {
isDarkMode,
toggleIsDarkMode,
};
return (
<DarkModeContext.Provider value={value}>
{children}
</DarkModeContext.Provider>
);
};
*** App.js***
import { useContext } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Navbar from "../components/Navbar";
import MainSection from "../components/MainSection/MainSection";
import CountryPage from "../components/CountryPage";
import { Wrapper } from "./App.styles";
import { DarkModeContext, DarkModeContextProvider } from "./DarkModeContext";
function App() {
const { isDarkMode } = useContext(DarkModeContext);
return (
<DarkModeContextProvider>
<Router>
<Wrapper dark={isDarkMode}>
<Navbar />
<Routes>
<Route path="/" element={<MainSection />}></Route>
<Route
path="/country/:countryCode"
element={<CountryPage />}
></Route>
</Routes>
</Wrapper>
</Router>
</DarkModeContextProvider>
);
}
***part of component***
import {useContext} from "react"
import { DarkModeContext } from "../../App/DarkModeContext";
export default function CountryPage() {
const { isDarkMode } = useContext(DarkModeContext);
return (
<CountryPageWrapper dark={isDarkMode}>
<StyledLink dark={isDarkMode} to="/">

What I'm noticing is that in DarkModeContextProvider you are calling setIsDarkMode on every render rather than wrapping that method in another function (you should also be passing the boolean value and not a function updating it). Try updating that line to:
const toggleIsDarkMode = () => { setIsDarkMode(!isDarkMode) };
Assuming you meant the toggler to be a function, this should prevent your re-rendering loop as the setter is only getting called when the toggle is called into action.

You can't create a context provider and consume its value in the same component.
For example in App right now you have this:
// ...
const { isDarkMode } = useContext(DarkModeContext);
return (
<DarkModeContextProvider>
<Router>
<Wrapper dark={isDarkMode}>
// ...
This doesn't work, because where is useContext(DarkModeContext) getting the value of its context from? It hasn't been rendered yet, until you render <DarkModeContextProvider> which happens after trying to get the value of isDarkMode from the context. This is backwards.
One easy way to to fix this could be to simply move your <DarkModeContextProvider> out of App and into where you render <App />. I assume you're using React DOM somewhere to render your app, so where you are using that try something like this:
ReactDOM.render((
<DarkModeContextProvider>
<App />
</DarkModeContextProvider>
), document.getElementById('root'));

Related

How to handle multiple Contexts using React Context API?

I have a question about React's Context API. My coding level with React is beginner.
I am building an application that has 8 contexts and they may multiply in the future of the project. They are basic CRUD contexts for the different elements of my application without much complexity.
As I am writing my application I notice that a nested context hell is created in my App.js
To give more information I will explain a portion of the app. I have a Context for CRUD actions for Coaches, Athletes, Courts etc.
In my folder structure under /src directory I have a /context directory and inside I have a separate folder for each entity. Let's take Coaches as an example. In the /src/context/coach directory I have 3 files. A coachContext.js, a coachReducer.js and a CoachState.js
Contents of coachContext.js file:
import { createContext } from "react";
const coachContext = createContext();
export default coachContext;
Contents of coachReducer.js file:
const coachReducer = (state, action) => {
switch (action.type) {
case "GET_COACHES":
return {
...state,
coaches: action.payload,
};
case "SET_CURRENT_COACH":
return {
...state,
coach: action.payload,
loading: false,
};
default:
return state;
}
};
export default coachReducer;
Contents of CoachState.js file:
import { useReducer } from "react";
import coachContext from "./coachContext";
import coachReducer from "./coachReducer";
const CoachState = (props) => {
const initialState = {
coaches: [],
coach: [],
loading: false,
};
const [state, dispatch] = useReducer(coachReducer, initialState);
// Function to Add coach
// Function to Delete coach
// Function to Set current coach
// Function to clear current coach
// Function to Update coach
return (
<coachContext.Provider
value={{
coaches: state.coaches,
coach: state.coach,
loading: state.loading,
}}
>
{props.children}
</coachContext.Provider>
);
};
export default CoachState;
The same goes for Athletes context, Courts context and all other elements of my application.
Finally, in my App.js I have:
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./pages/Home";
import Coaches from "./pages/Coaches";
import Athletes from "./pages/Athletes";
import Courts from "./pages/Courts";
import CoachState from "./context/coach/CoachState";
import AthleteState from "./context/athlete/AthleteState";
import CourtState from "./context/court/CourtState";
function App() {
return (
<CourtState>
<AthleteState>
<CoachState>
<Router>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/coaches" component={Coaches}></Route>
<Route exact path="/athletes" component={Athletes}></Route>
<Route exact path="/courts" component={Courts}></Route>
</Switch>
</Router>
</CoachState>
</AthleteState>
</CourtState>
);
}
export default App;
When I finish writing my other Contexts as you can understand they will wrap the Router as all current states do. So there is going to be a big nesting "problem".
I would like any advice as to how could I resolve this nested contexts issue? Did I make the correct decision of developing my app using Context API instead of Redux?
Instead of using multiple context providers then useContext to get each value from each context provider, you can add all of the needed values within one context provider then use a custom hook to fetch the data or function that you need
this decreases the amount of context providers used, it doesn't decrease them to 1 provider since not all logic is going to be shared or common within one provider and another
I have used Kent C. Dodds' blog post "How to use React Context effectively" as a reference to write context providers efficiently.
example: (basic counter example but I'll explain the workflow)
const MainContext = createContext(null);
const MyComponent = (props) => {
const [counter, updateCounter] = useState(0);
const increment = () => {
updateCounter(counter + 1);
}
const decrement = () => {
updateCounter(counter - 1);
}
return(
<MainContext.Provider value={{counter, increment, decrement}}>
{children}
</MainContext.Provider>
)
}
const useCountNumber = () => {
const context = useContext(MainContext);
if(context === undefined || context === null) {
throw new Error('useCounter is not within MainContext scope');
}
else {
return context.counter;
}
}
const useIncrementCount = () => {
const context = useContext(MainContext);
if(context === undefined || context === null) {
throw new Error('useIncrementCount is not within MainContext scope');
}
else {
return context.increment;
}
}
const useDecrementCount = () => {
const context = useContext(MainContext);
if(context === undefined || context === null) {
throw new Error('useDecrementCount is not within MainContext scope');
}
else {
return context.decrement;
}
}
// in component you wish to use those values
const MyCounter = () => {
const count = useCountNumber();
const increment = useIncrementCount();
const decrement = useDecrementCount();
return(
<div>
{count}
<button onClick={increment}> +1 </button>
<button onClick={decrement}> -1 </button>
</div>
);
}
I have used this in production, use one context provider and you put values inside of that single provider. This is manageable for a small set of functions but as it gets bigger then I would recommend to use something like redux or another state management library
Also consider using useMemo for memoizing some state elements and useReducer to utilize a function to optimize performance of your context if it is triggering deep updates
You could use this npm package react-pipeline-component
Your code would be like this:
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./pages/Home";
import Coaches from "./pages/Coaches";
import Athletes from "./pages/Athletes";
import Courts from "./pages/Courts";
import CoachState from "./context/coach/CoachState";
import AthleteState from "./context/athlete/AthleteState";
import CourtState from "./context/court/CourtState";
import {Pipeline, Pipe} from 'react-pipeline-component'
function App() {
return (
<Pipeline components={[
<CourtState children={<Pipe />} />,
<AthleteState children={<Pipe />} />,
<CoachState children={<Pipe />} />,
<Router children={<Pipe />} />,
<Switch children={<Pipe />} />,
<>
<Route exact path="/" component={Home}></Route>
<Route exact path="/coaches" component={Coaches}></Route>
<Route exact path="/athletes" component={Athletes}></Route>
<Route exact path="/courts" component={Courts}></Route>
</>
]}/>
);
}
export default App;

React + Redux + Storybook: How to use connected react router's useParams when writing storybook stories?

I have a react component that grabs an id from the route and uses that to load some data and populate the redux state.
I am using useParams from 'react-router' to do this.
import { useParams } from 'react-router'
import { usePreload } from './hooks'
import Display from './Display'
const Overview = () => {
const { id } = useParams()
const { data } = usePreload(id) // uses useEffect to preload the data with the given id
return <Display data={data} />
}
export default Overview
I've got a story
import Overview from './Overview'
import preloadData from './decorators/preloadData'
export default {
title: 'Redux/scenes/Overview',
decorators: [preloadData()],
component: Overview,
argTypes: {}
}
const Template = args => <Overview {...args} />
export const Default = Template.bind({})
The preloadData decorator is simply
import { usePreload } from '../hooks'
import { data } from './fixtures'
const Loaded = ({ children }) => {
useSubmissionsPreload(data.id) // loads the site data into the state
return <>{children}</>
}
const preloadData = () => Story => (
<Loaded>
<Story />
</Loaded>
)
export default preloadData
The code all works fine when actually running in the site but when running within a story there is no :id in the path for useParams to pick up.
For now I am just going to skip this story and just test the Display component, but the completist in me demands to know how to get this to work.
I also had the problem and the comment from De2ev pointed me in the right direction. It did however not work directly and I had to make slight changes. In the end it worked with the following code:
import React from "react";
import { Meta } from "#storybook/react";
import MyComponent from "./MyComponent";
import { MemoryRouter, Route} from "react-router-dom";
export default {
title: "My Title",
component: MyComponent,
decorators: [(Story) => (
<MemoryRouter initialEntries={["/path/58270ae9-c0ce-42e9-b0f6-f1e6fd924cf7"]}>
<Route path="/path/:myId">
<Story />
</Route>
</MemoryRouter>)],
} as Meta;
export const Default = () => <MyComponent />;
I've faced the same problem with Storybook 6.3+ and React Router 6.00-beta and had to wrap the <Route> with <Routes></Routes> for it to work.
import React from "react";
import { Meta } from "#storybook/react";
import MyComponent from "./MyComponent";
import { MemoryRouter, Routes, Route} from "react-router";
export default {
title: "My Title",
component: MyComponent,
decorators: [(Story) => (
<MemoryRouter initialEntries={["/path/58270ae9-c0ce-42e9-b0f6-f1e6fd924cf7"]}>
<Routes>
<Route path="/path/:myId" element={<Story />}/>
</Routes>
</MemoryRouter>)],
} as Meta;
export const Default = () => <MyComponent />;
We have faced similar challenge when trying to create storybook for one of the pages. We found solution published on Medium -> link. All credits and special thanks to the author.
Solution is using MemoryRouter available in react-router.
In our solution we used storybook Decorators which return the story wrapped by MemoryRouter and Router ->
return ( <MemoryRouter initialEntries={["/routeName/param"]} <Route component={(routerProps) => <Story {...routerProps} />} path="/routeName/:paramName"/> </MemoryRouter>)
I hope this helps everyone who experienced the same challenge.
Faced the same issue and completed as below
export default {
title: 'Common/Templates/Template Rendering',
component: CasePage
}
// 👇 We create a “template” of how args map to rendering
const Template: Story<any> = (args: any) => {
const { path } = args
return (
<MemoryRouter initialEntries={path}>
<Route
component={(routerProps: any) => <CasePage {...routerProps} />}
path="/dcp/:caseId"
/>
</MemoryRouter>
)
}
export const TemplateBoxesRendering = Template.bind({})
TemplateBoxesRendering.args = { path: ['/dcp/FX77777'] }
export const TemplateBoxes = Template.bind({})
TemplateBoxes.args = { path: ['/dcp/FX22222'] }

Simple logout component throws "Cannot update a component from inside the function body of a different component"

This little Logout.jsx component logs-out the user...
import React from 'react';
import { Redirect } from 'react-router';
import { useDispatch } from 'react-redux';
import { userLogout } from '../redux/actions/authActions';
const Logout = ({ to = '/loginForm' }) => {
const dispatch = useDispatch();
dispatch(userLogout());
return <Redirect to={to} />;
};
export default Logout;
and is used in path /logout thus:
<Switch>
...
<Route exact path="/logout" component={Logout} />
In the console it gives the dreaded (and apparently serious) message:
Cannot update a component from inside the function body of a different
component
Can someone spot why, and how to fix it?
Using react 16.13.0
I think it's just a logical mistake causing this error to pop up from another component (than Logout), try logging out once:
const Logout = ({ to = '/loginForm' }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(userLogout());
}, [dispatch]);
return <Redirect to={to} />;
};
You don't want to dispatch (or logout) on every component render

Flow React: Cannot create element because React.Component [1] is not a React component

I just started to make use of flow a few weeks ago and from a week ago i've been getting a flow error on which i've no idea how to fix.
The code goes like this:
// #flow
import React, { Component } from "react";
import { Redirect, Route } from "react-router-dom";
import CookieStorage from "./../services/CookieStorage";
import type { Component as ComponentType } from "react";
type Props = {
component: ComponentType<any, any>
}
class ProtectedRoute extends Component<Props> {
render() {
const isAuthenticated = this.isAuthenticated();
const {...props} = this.props;
const AuthorizedComponent = this.props.component;
return (
<Route
{...props}
render={props => (
isAuthenticated ?
<AuthorizedComponent {...props} /> :
<Redirect to="/"/>
)}
/>
);
}
isAuthenticated(): boolean {
const data = CookieStorage.get("foobar");
return data !== null;
}
}
export default ProtectedRoute;
In here flow throws this error:
Error:(23, 8) Cannot create `AuthorizedComponent` element because `React.Component` [1] is not a React component.
I don't know if i am doing a wrong import type or a wrong type declaration for the component that is to be rendered when the authentication example is ok.
I've copied this code from a website i don't remember where, but he was making use of this snippet using const {component: Component} = this.props and render it as <Component {...props} /> which for me it seems a little ambiguous, which is why i changed the declaration a bit to make it easy to understand when reading, but still even doing the exact same code like the snipped where i copied this code, flow still throws that error.
I've made a gist of this in case someone would knows a solution for this and would like to make a change, if no one is able to help me fix this in here, then i will send a ticket issue to their project using this gist
Try to use React.ComponentType instead?
import type { ComponentType } from "react";
import React, { Component } from "react";
import { Redirect, Route } from "react-router-dom";
import CookieStorage from "./../services/CookieStorage";
type Props = {
component: ComponentType<any>
}
class ProtectedRoute extends Component<Props> {
render() {
const isAuthenticated = this.isAuthenticated();
const { component: AuthorizedComponent, ...props } = this.props;
return (
<Route
{...props}
render={props => (
isAuthenticated ?
<AuthorizedComponent {...props} /> :
<Redirect to="/"/>
)}
/>
);
}
isAuthenticated(): boolean {
const data = CookieStorage.get("foobar");
return data !== null;
}
}
export default ProtectedRoute;
See https://flow.org/en/docs/react/types/#toc-react-componenttype

Detect Route Change with react-router

I have to implement some business logic depending on browsing history.
What I want to do is something like this:
reactRouter.onUrlChange(url => {
this.history.push(url);
});
Is there any way to receive a callback from react-router when the URL gets updated?
You can make use of history.listen() function when trying to detect the route change. Considering you are using react-router v4, wrap your component with withRouter HOC to get access to the history prop.
history.listen() returns an unlisten function. You'd use this to unregister from listening.
You can configure your routes like
index.js
ReactDOM.render(
<BrowserRouter>
<AppContainer>
<Route exact path="/" Component={...} />
<Route exact path="/Home" Component={...} />
</AppContainer>
</BrowserRouter>,
document.getElementById('root')
);
and then in AppContainer.js
class App extends Component {
componentWillMount() {
this.unlisten = this.props.history.listen((location, action) => {
console.log("on route change");
});
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<div>{this.props.children}</div>
);
}
}
export default withRouter(App);
From the history docs:
You can listen for changes to the current location using
history.listen:
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
The location object implements a subset of the window.location
interface, including:
**location.pathname** - The path of the URL
**location.search** - The URL query string
**location.hash** - The URL hash fragment
Locations may also have the following properties:
location.state - Some extra state for this location that does not reside in the URL (supported in createBrowserHistory and
createMemoryHistory)
location.key - A unique string representing this location (supported
in createBrowserHistory and createMemoryHistory)
The action is one of PUSH, REPLACE, or POP depending on how the user
got to the current URL.
When you are using react-router v3 you can make use of history.listen() from history package as mentioned above or you can also make use browserHistory.listen()
You can configure and use your routes like
import {browserHistory} from 'react-router';
class App extends React.Component {
componentDidMount() {
this.unlisten = browserHistory.listen( location => {
console.log('route changes');
});
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<Route path="/" onChange={yourHandler} component={AppContainer}>
<IndexRoute component={StaticContainer} />
<Route path="/a" component={ContainerA} />
<Route path="/b" component={ContainerB} />
</Route>
)
}
}
Update for React Router 5.1+.
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function SomeComponent() {
const location = useLocation();
useEffect(() => {
console.log('Location changed');
}, [location]);
...
}
react-router v6
In react-router v6, this can be done by combining the useLocation and useEffect hooks
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])
...
}
For convenient reuse, you can do this in a custom useLocationChange hook
// runs action(location) on location, i.e. route, change
const useLocationChange = (action) => {
const location = useLocation()
React.useEffect(() => { action(location) }, [location])
}
const MyComponent1 = () => {
useLocationChange((location) => {
console.log('handle route change here', location)
})
...
}
const MyComponent2 = () => {
useLocationChange((location) => {
console.log('and also here', location)
})
...
}
If you also need to see the previous route on change, you can combine with a usePrevious hook
const usePrevious = (value) => {
const ref = React.useRef()
React.useEffect(() => { ref.current = value })
return ref.current
}
const useLocationChange = (action) => {
const location = useLocation()
const prevLocation = usePrevious(location)
React.useEffect(() => {
action(location, prevLocation)
}, [location])
}
const MyComponent1 = () => {
useLocationChange((location, prevLocation) => {
console.log('changed from', prevLocation, 'to', location)
})
...
}
It's important to note that all the above fire on the first client route being mounted, as well as subsequent changes. If that's a problem, use the latter example and check that a prevLocation exists before doing anything.
If you want to listen to the history object globally, you'll have to create it yourself and pass it to the Router. Then you can listen to it with its listen() method:
// Use Router from react-router, not BrowserRouter.
import { Router } from 'react-router';
// Create history object.
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
// Listen to history changes.
// You can unlisten by calling the constant (`unlisten()`).
const unlisten = history.listen((location, action) => {
console.log(action, location.pathname, location.state);
});
// Pass history to Router.
<Router history={history}>
...
</Router>
Even better if you create the history object as a module, so you can easily import it anywhere you may need it (e.g. import history from './history';
This is an old question and I don't quite understand the business need of listening for route changes to push a route change; seems roundabout.
BUT if you ended up here because all you wanted was to update the 'page_path' on a react-router route change for google analytics / global site tag / something similar, here's a hook you can now use. I wrote it based on the accepted answer:
useTracking.js
import { useEffect } from 'react'
import { useHistory } from 'react-router-dom'
export const useTracking = (trackingId) => {
const { listen } = useHistory()
useEffect(() => {
const unlisten = listen((location) => {
// if you pasted the google snippet on your index.html
// you've declared this function in the global
if (!window.gtag) return
window.gtag('config', trackingId, { page_path: location.pathname })
})
// remember, hooks that add listeners
// should have cleanup to remove them
return unlisten
}, [trackingId, listen])
}
You should use this hook once in your app, somewhere near the top but still inside a router. I have it on an App.js that looks like this:
App.js
import * as React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Home from './Home/Home'
import About from './About/About'
// this is the file above
import { useTracking } from './useTracking'
export const App = () => {
useTracking('UA-USE-YOURS-HERE')
return (
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
)
}
// I find it handy to have a named export of the App
// and then the default export which wraps it with
// all the providers I need.
// Mostly for testing purposes, but in this case,
// it allows us to use the hook above,
// since you may only use it when inside a Router
export default () => (
<BrowserRouter>
<App />
</BrowserRouter>
)
I came across this question as I was attempting to focus the ChromeVox screen reader to the top of the "screen" after navigating to a new screen in a React single page app. Basically trying to emulate what would happen if this page was loaded by following a link to a new server-rendered web page.
This solution doesn't require any listeners, it uses withRouter() and the componentDidUpdate() lifecycle method to trigger a click to focus ChromeVox on the desired element when navigating to a new url path.
Implementation
I created a "Screen" component which is wrapped around the react-router switch tag which contains all the apps screens.
<Screen>
<Switch>
... add <Route> for each screen here...
</Switch>
</Screen>
Screen.tsx Component
Note: This component uses React + TypeScript
import React from 'react'
import { RouteComponentProps, withRouter } from 'react-router'
class Screen extends React.Component<RouteComponentProps> {
public screen = React.createRef<HTMLDivElement>()
public componentDidUpdate = (prevProps: RouteComponentProps) => {
if (this.props.location.pathname !== prevProps.location.pathname) {
// Hack: setTimeout delays click until end of current
// event loop to ensure new screen has mounted.
window.setTimeout(() => {
this.screen.current!.click()
}, 0)
}
}
public render() {
return <div ref={this.screen}>{this.props.children}</div>
}
}
export default withRouter(Screen)
I had tried using focus() instead of click(), but click causes ChromeVox to stop reading whatever it is currently reading and start again where I tell it to start.
Advanced note: In this solution, the navigation <nav> which inside the Screen component and rendered after the <main> content is visually positioned above the main using css order: -1;. So in pseudo code:
<Screen style={{ display: 'flex' }}>
<main>
<nav style={{ order: -1 }}>
<Screen>
If you have any thoughts, comments, or tips about this solution, please add a comment.
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Sidebar from './Sidebar';
import Chat from './Chat';
<Router>
<Sidebar />
<Switch>
<Route path="/rooms/:roomId" component={Chat}>
</Route>
</Switch>
</Router>
import { useHistory } from 'react-router-dom';
function SidebarChat(props) {
**const history = useHistory();**
var openChat = function (id) {
**//To navigate**
history.push("/rooms/" + id);
}
}
**//To Detect the navigation change or param change**
import { useParams } from 'react-router-dom';
function Chat(props) {
var { roomId } = useParams();
var roomId = props.match.params.roomId;
useEffect(() => {
//Detect the paramter change
}, [roomId])
useEffect(() => {
//Detect the location/url change
}, [location])
}
Use the useLocation() Hook to detect the URL change and put it in dependency array in useEffect() this trick worked for me
const App = () => {
const location = useLocation();
useEffect(() => {
window.scroll(0,0);
}, [location]);
return (
<React.Fragment>
<Routes>
<Route path={"/"} element={<Template/>} >
<Route index={true} element={<Home/>} />
<Route path={"cart"} element={<Cart/>} />
<Route path={"signin"} element={<Signin/>} />
<Route path={"signup"} element={<Signup/>} />
<Route path={"product/:slug"} element={<Product/>} />
<Route path={"category/:category"} element={<ProductList/>} />
</Route>
</Routes>
</React.Fragment>
);
}
export default App;
You can use the useLocation with componentDidUpdate for getting the route change for class component and useEffect for functional component
In Class component
import { useLocation } from "react-router";
class MainApp extends React.Component {
constructor(props) {
super(props);
}
async componentDidUpdate(prevProps) {
if(this.props.location.pathname !== prevProps.location.pathname)
{
//route has been changed. do something here
}
}
}
function App() {
const location = useLocation()
return <MainApp location={location} />
}
In functional component
function App() {
const location = useLocation()
useEffect(() => {
//route change detected. do something here
}, [location]) //add location in dependency. It detects the location change
return <Routes>
<Route path={"/"} element={<Home/>} >
<Route path={"login"} element={<Login/>} />
</Routes>
}
React Router V5
If you want the pathName as a string ('/' or 'users'), you can use the following:
// React Hooks: React Router DOM
let history = useHistory();
const location = useLocation();
const pathName = location.pathname;

Resources