react router, how to make page refresh on url change? - reactjs

I am making a movie app, where the each movie has its own dedicated page. On this page I have provided similar movies that can be clicked on and ideally would take you to its own movie page. I have a route that looks like this
<Route name="movie" path="/movie/:movieID" exact ><MoviePage /></Route>
the link to this route is in each and every movie component that I have created. The issue is that when I click on a similar movie the url changes to the url of the similar movie and I am able to access the similar movie's properties in the developer console, however the page itself does not change. But once refreshed the page's contents change to those that correspond to the url.
I cannot find a way to force refresh the page in order to get the new movie's information to be displayed.

The issue is likely that the MoviePage component needs to react to the movieID route param updating.
Given Route:
<Route name="movie" path="/movie/:movieID" exact>
<MoviePage />
</Route>
MoviePage:
Use an useEffect hook to handle any logic/data fetching/etc based on the movieID param updating.
const { movieID } = useParams();
React.useEffect(() => {
// movieID on initial render or subsequent render when updated
// logic to use movieID and resynchronize any data.
}, [movieID]);
If still using class components, then use the componentDidUpdate lifecycle method. (Assumes MoviePage wrapped in withRouter HOC to receive route props, specifically the match prop)
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.movieID !== this.props.match.params.movieID) {
// logic to use movieID and resynchronize any data.
}
}

Related

Move between pages without losing pages content - React JS

I'm trying to navigate between pages without losing pages data. For instance, I have a page that contains many input fields, if a user filled all these fields and tried to move to another page and return back to the first one, all the inputs are gone. I am using react-router-dom but didn't find out a way to prevent that.
What I've done till now :
import { Route, Switch, HashRouter as Router } from "react-router-dom";
<Router>
<Switch>
<Route path="/home" exact component={Home} />
<Route path="/hello-world" exact component={HellWorld} />
</Switch>
</Router>
Home Component :
navigateToHelloWorld= () => {
this.props.history.push('/hello-world')
};
Hello World Component :
this.props.history.goBack();
I don't know that that can be supported in such generality. I would just store all state variable values in localStorage, and restore from there when values are present on component render (when using useState then as the default value). Something like this:
const Home = () => {
const [field, setField] = useState(localStorage.field || '');
const handleUpdate = (value) => {
setField(value);
localStorage.field = value;
}
// also add a submit handler incl. `delete localStorage.field`;
return ... // your input fields with handleUpdate as handler.
}
Generally, all you need to do is to store your data in someplace, either a component that doesn't unmount, like the component you are handling your routes in, which is not a good idea actually but it works!
another way is to use some kind of state manager like 'mobx','redux','mst', or something which are all great tools and have great documentation to get you started
another alternative is to store your data in the browser, for your example session storage might be the one to go for since it will keep data until the user closes the tab and you can read it in each component mount.

React JS page keeps refreshing when using the back button

I have 2 React JS pages (A & B), when I go from A->B and back to A, page A is refreshed every time. I was under the impression that page is not destroyed. All related questions on StackOverflow seems to be about the opposite problem.
The reason the page refreshes is because useEffect() is called when the back button is pressed despite using useState() to prevent this. I even tried replacing 'refresh' with a 'props.id' parameter (that never changes). See code below:
Here's my code to page A:
import { useHistory, useParams } from "react-router-dom";
import React, { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";
export default function Test(props) {
const [refresh, setRefresh] = useState(false);
useEffect(() => {
console.log("useEffect called: "+refresh);
setRefresh(true);
},[refresh]);
return (
<>
Hello from Test
<Link to="/test2">Test me</Link>
</>
);
}
I'm using react-router-dom: "^5.1.2", and import { BrowserRouter as Router } from "react-router-dom"; in App.js and specified:
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/test">
<Test id="1"/>
</Route>
<Route exact path="/test2">
<Test2 />
</Route>
.....
Does anyone know how to prevent useEffect() from being triggered when returning to page? The actual page A fetches using a REST call and display a long list of items and I do not want the page to refresh every time the user load page B to view item and then returns to the page.
You need to add a condition to useEffect.
If you only want to setRefresh to true if its false, then do something like:
useEffect(() => {
if(!refresh) setRefresh(true)
}, [refresh])
Since you are starting with const [refresh, setRefresh] = useState(false) and are not changing refresh anywhere else in the component, this will run once everytime the component loads (not renders).
If you want to run this once in the lifetime of the app and not the component, you need to persist the information outside the component, by either lifting the state up to a parent component and persisting the information is something like localstorage/sessionstorage.
You could then extract this information whenever your component loads and set the refresh state variable accordingly.
Let's say you just want to setRefresh to true once. Add this useEffect:
useEffect(() => {
let persistedRefresh
try {
persistedRefresh = !!JSON.parse(window.localstorage.getItem('THE_KEY_TO_REFRESH_VALUE'))
} catch(error) {
persistedRefresh = false
}
setRefresh(persistedRefresh)
}, [])
This useEffect will run whenever the component loads, and update the state variable, triggering the previous useEffect.
We also need to modify the previous useEffect:
useEffect(() => {
if(!refresh) {
setRefresh(true)
window.localstorage.setItem('THE_KEY_TO_REFRESH_VALUE', JSON.stringify(true))
}
}, [refresh])
In this useEffect we are updating the persisted value so that whenever the component loads,
it will check the persisted value,
refresh if needed, and
update the persisted value for the next loads.
This is how you do it without any extra dependencies.
I can see that you're importing the very useful useHistory prop, but not doing much with it. It can actually be used to check if a user is navigating to the page by using the back button. useHistory()'s action properly will tell you everything you need. If the back button was used, action will be "POP". So you can put some logic into your useEffect to check for that:
const history = useHistory();
React.useEffect(() => {
if (history.action === "POP")
console.log("Back button used. Not running stuff");
else console.log("useEffect called in home");
}, []);
Here is a Sanbox. And here you can actually test the sandbox code in a dedicate browser window: https://okqj3.csb.app/
Click the "About" link and then use the back button to go back to "Home", in the console you will see how the Home element's useEffect function catches it.
Solution 1 (Correct way)
Use Stateless components and have a common super state (Redux will be of great assistance), and bind you page/data to common state so even if the state changes, the page will always render the current state creating an illusion of page retaining the state (I used it to run large queries and store progress/result in redux so even if I open another page and come back then also I see query in progress or result).
However I am not really sure what your use case is.
Solution 2 (slightly wrong way)
Use React.memo,You can use it when you don't want to update a component that you think is static
For function Components:
const Mycomponents = React.memo(props => {
return <div>
No updates on this component when rendering, use useEffect to verify too
</div>;
});
You shouldn't be defining any method/functionality/dynamic calculation inside this kind of method just to avoid getting irregular data

Fetching data from API and react-router-dom on refresh / direct link

I'm learning redux and routing and history with react-router-dom and react-connected-router for an app. I have an api and I do my fetchData in componentDidMount():
componentDidMount() {
const { onFetchData, prodPerPage, currentPage, filters } = this.props;
onFetchBooks(prodPerPage, currentPage, filters);
}
In my pagination component I use a link that routes to the page which works fine onClick because I re-fetch data on click where currentPage becomes the pageIndex so data is fetched for that page.
Here is my link:
<Link to={`/products/page/${item.page}`}>
<PaginationItem {...item} />
</Link>
Here is my route:
<Route exact path="/products/page/:page" component={ProductsPage} />
If I reload my page my currentPage is set to 1 from initialState.
On Back/Forward my page again loads data for page 1.
How can I set my currentPage to be whatever I navigate to? So if my link is https://localhost:3000/products/page/3 then set currentPage to 3 and fetch the correct data?
Note: If I try to load https://localhost:3000/products/page/3sddada it doesn't redirect to 404, how do I fix that too?
Thanks in advance.
We need to get access to the dynamic pageid being passed into our route and use it to query the correct page from the API. This is easy to do using react-router-dom. The library passes in a prop called match into every route that is rendered. Inside this match object is another object called params. This holds all matching params where the key is the name we specified when creating the route and the value is the actual value in the URL.
componentDidMount() {
const { page} = this.props.match.params;
this.setState({currentPage:page});
const { onFetchData, prodPerPage, filters } = this.props;
onFetchBooks(prodPerPage, currentPage, filters);
}
React-router v4 now allows you to use regexes to match params -- https://reacttraining.com/react-router/web/api/Route/path-string
<Switch>
<Route exact path="/products/page/:page(\\d+)" component={ProductsPage}/>
<Route exact path="/products/page/:page(\\w+)" component={ErrorRedirectPage}/>
</Switch>

Component render() triggered after history.push but page is blank

I want the user to be redirected to the resources list after he deleted an item on its show page. I 've read a lot of SO Q&A on the topic, but I think I have a different issue as my routes and component got hit the right way after history.push
I tracked code execution through debugger till component render and still don't understand why nothing is returned
Here are my routes in my App component (wrapped this way<Router><App /></Router>) component :
<Route component={AppHeader} />
{["/articles/:id/edit", "/articles/new"].map((path, index) =>
<Route key={index} exact path={path} component{ArticleForm}/>
)}
<Route exact path="/articles/:id" component={Article}/>
{["/", "/articles"].map((path, index) =>
<Route key={index} exact path={path} component{ArticlesList}/>
)}
I use redux so Router and ArticleList are exported this way :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Component))
In AppHeader component, a delete button is provided, if user is on show or edit page. When clicking on the link, following method is triggered :
class AppHeader extends Component {
...
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.props.history.push("/")
}
...
}
Then Route with ArticlesList is triggered and should render this component. Here is what happens (breakpoints all the way in render methods):
URL is updated
Router is rendered
App header is rendered
Article list is rendered
Article list re-rendered with fecth from API (state populated with list)
Article list re-rendered with componentDidUpdate
BUT page stays blank ... I am using this.props.history.push("/") in other components and it works fine (list get re-rendered and displayed). See blank page and console.logs :
If I manually reload the page, it renders normally.
What is preventing any component to be displayed (DOM is nearly empty, I only get my empty <div id="root"></div>) in this case ?
Do one thing ,to displaying Article list use filter to update list state
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})
}
Change :
this.props.history.push("/")
To :
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})

State change not re-rendering component

I couldn't find the answer anywhere because nothing was working for me, so I'm starting a new topic. Please, don't mark it as a duplicate
Router.js:
<Switch>
<Route path="/" exact component={Home} />
<Route exact path="/p/:uid" component={Profile} />
</Switch>
Profile.js
constructor(props) {
super(props);
this.state = {
loading: true,
profile_user_id: this.props.match.params.uid,
};
}
Then, later in Profile.js, I trigger a fetch to get data from backend and save this data to state using this.setState({ ... }). When the Profile component is rendered, everything looks fine.
In Router.js, there is also a Link:
<Link to={"/p/" + this.state.ntuser.tag}>Profile</Link>
.. which should go to your own profile. So when your user ID is 1, and you visit the profile of the user with id 22, your URL will be /p/user22 and the link will point to /p/user1.
The problem is that even though profiles are rendered nicely, Profile component does not become re-rendered when you click the link (which should direct you to /p/user1 and show your profile instead). I tried to save location from react-router to state as well, so every time URL changes it will be caught in componentWillReceiveProps() and inside I update state. But still nothing. Any ideas?
PS: I'm using React.Component
console.log(this.props.match.params.uid) in constructor, componentDidMount() and componentDidUpdate() (UNSAFE_componentWillReceiveProps() is deprecated)
Number and places (of log calls) will tell you if component is recreated (many construcor calls) or updated (cDM calls). Move your data fetching call accordingly (into cDM or cDU ... or sCU).
You can save common data in component above <Router/> (f.e. using context api) - but this shouldn't be required in this case.
Solution
You can update state from changed props using componentDidUpdate() or shouldComponentUpdate(). componentDidUpdate should be protected with conditions to prevent infinite loop. See docs.
Simplest solution:
componentDidUpdate(prevProps) {
if (this.props.some_var !== prevProps.some_var) {
// prop (f.e. route '.props.match.params.uid') changed
// setState() - f.e. for 'loading' conditional rendering
// call api - use setState to save fetched data
// and clearing 'loading' flag
}
}
shouldComponentUpdate() can be used for some optimalizations - minimize rerenderings.
Had same problem in my project, Didn't find any good workable idea exept making somethink like this:
import {Profile} from "../Profile "
<Link to={`your href`}
onClick={userChanged}/>
..........
function userChanged() {
const userCard= new Profile();
userCard.getProfileData();
}
First of all you need to make your Profile component as export class Profile extends React.Component (export without default).
getProfileData is method where i get data from my api and put it state. This will rerender your app
Here is what happens:
You go to /p/user22.
React renders <Route exact path="/p/:uid" component={Profile} />.
The Route renders the Profile component for the first time.
The Profile component calls the constructor, at the time, this.props.match.params.uid is equal to "user22". Therefore, this.state.profile_user_id is now "user22".
You click on the link to /p/user1.
React renders <Route exact path="/p/:uid" component={Profile} />, which is the same Route.
The Route then rerenders the same Profile component but with different props.
Since, its the same Profile component, it does not update the state.
This explains why you still see the profile of user22

Resources