Component not rerendering on route changing - reactjs

I am using React Hooks and my state (moduleName) is not getting updated even though the prop(which i get from route)changes? I need to use moduleName to useEffect Dependencies to make Api Call.
I am also using withRouter at my component but it doesnt seem to rerender my component when route changes. My App.js looks like this
<Router>
<Fragment>
<Switch>
<Route path="/" exact={true} component={Login} />
<Route component={Routes} />
</Switch>
</Fragment>
</Router>
and at the component i need to re render on route changei have this
const ListView = (props) =>{
const [moduleName, setModuleName] =useState(props.match.params.moduleName);
useEffect(() => {
//api call here
}, [moduleName]);
}
export default connect(
mapStateToProps,
null
)(withRouter(ListView));**

You should listen prop changes inside the useEffect Hook, and you don't need to hold state for a prop, so you can delete useState hook
const ListView = (props) =>{
useEffect(() => {
let moduleName = props.match.params.moduleName
if(moduleName) {
console.log(props.match.params.moduleName)
// do something when moduleName changes
// api call
axios.get('someurl/'+moduleName)
}
}, [props.match.params.moduleName]);
}
export default withRouter(connect(mapStateToProps,null)(ListView));

Related

React control flow for manually entered route

I have a App component with all the routes defined as below;
function App() {
//some logic for state including canShow which is a boolean and shows routes only if it is true
{canShow && (
<Route exact path="/Route1">
<Comp1 />
</Route>
<Route exact path="/Route2">
<Comp2 />
</Route>
)
}
Now say if user is currently on localhost/#/Route1 and manually enters URL i.e. say to localhost/#/Route2, the control seems to be jumping directly to Comp2
I also have props.history.listen() setup in a child component of App (outside all the Route definitions). So basically this listen is in a direct child component of App.
Is there any way by which on manually entering the URL, I can ensure that the control first always goes to App.js...So that I can update the logic for setting "canShow" and if canShow is false, I do not render any child component and also the control does not go Comp1 or Comp2
Also control to props.history.listen callback when navigating via links, but with direct URL entry, it does not seem to be going to props.history.listen first.
You can use the useHistory hook to let your App component update on history changes. This hook returns the history object with a location property.
In the App component, you can add an useEffect to determine your canShow state and update it when needed.
import { useHistory } from 'react-router';
import { useEffect, useState } from 'react';
function App() {
const [canShow, setCanShow] = useState(true);
const history = useHistory();
useEffect(() => {
if (history.location.pathname === '/Route1') {
setCanShow(true);
}
}, [history])
//some logic for state including canShow which is a boolean and shows routes only if it is true
return canShow && (
<Route exact path="/Route1">
<Comp1 />
</Route>
<Route exact path="/Route2">
<Comp2 />
</Route>
);
}

Open a Modal when updating location in React

I'm working with React Router and I need to show a modal when the url locations change without refreshing the page. How can I do that approach ?
create a route for same component with extra param and add modal id param is exist in that component
<Route path="/users">
<Users />
</Route>
<Route path="/users/:x">
<Users />
</Route>
then
var {x} = useparam();
if(x)
//open modal
You can use the history API to subscribe to location changes. In the example below the history.listen() function returns an unsubscribing function so we can clean up after the component unmounts.
import React, { useEffect } from 'react'
import { useHistory } from 'react-router-dom'
const App = ({ history }) => {
const history = useHistory();
useEffect(() => history.listen(({action, location})) => {
//modal logic
}), []);
//...
}
export default App;

Will component Re-Render when the path is changed?

My App component definition looks as follows:
function App() {
return (
<Router>
<Navbar/>
<Switch>
<Route path="/howitworks">
<HowItWorks/>
</Route>
<Route path="/aboutus">
<AboutUs/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
<Footer/>
</Router>
)
}
I have a question regarding to route and re-render.
For example, when I route from / to /howitworks, then the component <HowItWorks/> is going to be rendered. Routing back to / from /howitworks, will <Home/> component be re-rendered?
The <Home/> component contains only text. It does not contain any logic.
Update
I have created an example on https://codesandbox.io/s/react-router-forked-2mp45.
When you consider the about component, how it is defined:
import React, { useState } from "react";
const About = () => {
const [state, _] = useState(2);
React.useEffect(
(_) => {
console.log("state changed");
},
[state]
);
return (
<div>
<h2>About</h2>
</div>
);
};
export default About;
and every time when /aboutus is clicked, it shows always the message:
state changed
that means for me, every time when the path changed, then re-render will always happen.
Am I right?
Yes, re-render happens on path change. Re-render essentially means painting the screen again.
If you think about component being mounting again the component unmounts too on path change.
Here is an example reflecting that https://codesandbox.io/s/react-router-forked-nlqzg?file=/components/About.js

Re-render same component on route params change (react-router-dom)

I have code like this:
<BrowserRouter basname="/page">
<Switch>
<Route path="/test/:id">
<Page />
</Route>
</Switch>
</BrowserRouter>
When i switch from /page/test/1 to /page/test/2 the Page component won't re-rendering. I know the componentDidMount method won't be called but i want the Page component re-render.
How can i do that?
Try like this if you are using React v16.8 or above
import React, { useEffect } from "react";
const Page = (props) => {
useEffect(() => {
//Your code comes here to fetch the data from API
}, [props.match.params.id]);
return (<div>Layout</div>);
}
export default Page;
For Class components
<Route path="/page/:pageid" render={(props) => (
<Page key={props.match.params.pageid} {...props} />)
} />

React router dom redirect problem. Changes url, does not render component

Problem: When I use history.push(), I can see that browser changes url, but it does not render my component listening on the path. It only renders if I refresh a page.
App.js file:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { Provider } from "react-redux";
import PropTypes from "prop-types";
//Components
import LoginForm from "../LoginForm/LoginForm";
import PrivateRoute from "../PrivateRoute/PrivateRoute";
import ServerList from "../ServerList/ServerList";
const App = ({ store }) => {
const isLoggedIn = localStorage.getItem("userToken");
return (
<Router>
<Provider store={store}>
<div className="App">
{isLoggedIn !== true && (
<Route exact path="/login" component={LoginForm} />
)}
<PrivateRoute
isLoggedIn={!!isLoggedIn}
path="/"
component={ServerList}
/>
</div>
</Provider>
</Router>
);
};
App.propTypes = {
store: PropTypes.object.isRequired
};
export default App;
Inside my LoginForm, I am making a request to an API, and after doing my procedures, I use .then() to redirect my user:
.then(() => {
props.history.push("/");
})
What happens: Browser changes url from /login to /, but component listening on / route is not rendered, unless I reload page.
Inside my / component, I use useEffect() hook to make another request to API, which fetches data and prints it inside return(). If I console.log inside useEffect() it happens twice, I assume initial one, and when I store data from an API inside component's state using useState() hook.
EDIT: adding PrivateRoute component as requested:
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ component: Component, isLoggedIn, ...rest }) => {
return (
<Route
{...rest}
render={props =>
isLoggedIn === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/login" }} />
)
}
/>
);
};
export default PrivateRoute;
What I tried already:
1) Wrapping my default export with withRouter():
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginForm));
2) Creating custom history and passing it as prop to Router.
react-router-dom version is ^5.0.1. react-router is the same, 5.0.1
You have at two mistakes in your code.
You are not using <switch> component to wrap routes. So all routes are processed at every render and all components from each <route> are rendered.
You are using local store to exchange information between components. But change in local store is invisible to react, so it does not fire component re-rendering. To correct this you should use local state in App component (by converting it to class or using hooks).
So corrected code will look like
const App = ({ store }) => {
const [userToken, setUserToken] = useState(localStorage.getItem("userToken")); // You can read user token from local store. So on after token is received, user is not asked for login
return (
<Router>
<Provider store={store}>
<div className="App">
<Switch>
{!!userToken !== true && (
<Route exact path="/login"
render={props => <LoginForm {...props} setUserToken={setUserToken} />}
/>
)}
<PrivateRoute
isLoggedIn={!!userToken}
path="/"
component={ServerList}
/>
</Switch>
</div>
</Provider>
</Router>
);
};
And LoginForm should use setUserToken to change user token in App component. It also may store user token in local store so on page refresh user is not asked for login, but stored token is used.
Also be sure not to put anything between <Switch> and </Switch> except <Route>. Otherwise routing will not work.
Here is working sample

Resources