Reputation: 578
I'm trying to build a component that shows a list of blog posts. When a user clicks on a post, it renders the component that shows the details of the post. But when the user hits the back button in the browser, the previous component with posts list re-renders and it looses the previous state and scroll position. Is there a way that I Can save the previous state and the scroll position so that when a user hits the back button they are at the same position and the post list is not re-rendered and doesn't loose the scroll position too?
Here's my blog list component code:
import axios from "axios";
import { Link } from "react-router-dom";
class About extends React.Component {
state = { posts: [] };
componentDidMount() {
axios.get("https://jsonplaceholder.typicode.com/posts").then(res => {
this.setState({ posts: res.data.slice(0, 10) });
});
}
render() {
const { posts } = this.state;
const postsList = posts.length ? (
posts.map(post => {
return (
<div className="card" key={post.id}>
<div className="card-body">
<Link to={"/view/" + post.id}>
<h5 className="card-title">{post.title}</h5>
</Link>
<p className="card-text">{post.body}</p>
</div>
</div>
);
})
) : (
<div className="text-danger text-center">No Posts yet...</div>
);
return <div>{postsList}</div>;
}
}
export default About;
Here's is my blog details component:
import React from "react";
import { withRouter } from "react-router-dom";
import axios from "axios";
class PostDetail extends React.Component {
state = { post: null };
componentDidMount() {
let id = this.props.match.params.post_id;
axios.get("https://jsonplaceholder.typicode.com/posts/" + id).then(res => {
this.setState({ post: res.data });
});
}
render() {
const post = this.state.post ? (
<div className="card border-primary">
<div className="card-header">{this.state.post.title}</div>
<div className="card-body text-primary">
<p className="card-text">{this.state.post.body}</p>
</div>
</div>
) : (
<div className="text-center text-danger">Loading Post...</div>
);
return <div>{post}</div>;
}
}
export default withRouter(PostDetail);
Upvotes: 31
Views: 54523
Reputation: 89194
With React Router 6.4+, use the ScrollRestoration
component. It requires using a data router, such as one created by calling createBrowserRouter
(which is recommended for all new React Router web projects).
This component will emulate the browser's scroll restoration on location changes after loaders have completed to ensure the scroll position is restored to the right spot, even across domains.
To use it, simply render it once in the component for which the scroll position should be maintained when navigating back:
import { ScrollRestoration } from 'react-router-dom';
function About() {
return <>
{/* list here */}
<ScrollRestoration/>
</>;
}
Upvotes: 6
Reputation: 191
try the bellow code in your app.js
useEffect(() => {
const setScroll = () => {
window.onscroll = (e) => {
console.log("scrole r", window.scrollY);
window.scrollY > 12 ? localStorage.setItem("scroll_posistion", window.scrollY) : false;
};
}
let pos = localStorage.getItem("scroll_posistion");
window.scrollTo(0, pos);
window.addEventListener('scroll', setScroll);
// Don't forget to clean up the event listener when the component unmounts
return () => {
window.removeEventListener('scroll', setScroll);
};
}, [0]);
Upvotes: 0
Reputation: 398
here is the answer using hooks(functional component)
import axios from 'axios';
import { Link } from 'react-router-dom';
const Posts = () => {
const [posts, setPosts] = useState([]);
const postsList = posts.length;
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
setPosts(res.data.slice(0, 20));
});
}, []);
useEffect(() => {
if (posts.length) {
const scrollPosition = sessionStorage.getItem('scrollPosition');
if (scrollPosition) {
window.scrollTo(0, parseInt(scrollPosition, 10));
sessionStorage.removeItem('scrollPosition');
}
}
}, [posts]);
return (
<>
{postsList ? (
posts.map((post) => {
return (
<div className="card" key={post.id}>
<div className="card-body">
<Link
to={'/view/' + post.id}
onClick={() =>
sessionStorage.setItem('scrollPosition', window.pageYOffset)
}
>
<h5 className="card-title">{post.title}</h5>
</Link>
<p className="card-text">{post.body}</p>
</div>
</div>
);
})
) : (
<div className="text-danger text-center">No Posts yet...</div>
)}
<div>{postsList}</div>
</>
);
};
export default Posts;
https://stackblitz.com/edit/react-cbl7in?file=Posts.js
Upvotes: 7
Reputation: 121
Just in case, someone want to do the same on some specific HTML elements.
const scrollYPosition = document.getElementsByClassName('grid-body')[0].scrollTop;
this.setState({
toBeExpanded
},() => {
document.getElementsByClassName('grid-body')[0].scrollTo(0, scrollYPosition);
});
Upvotes: 3
Reputation:
You have to store the scroll position in state
on click of post with the use of window.pageYOffset
this.setState({
scrollPosition: window.pageYOffset
});
And once you click on back button at that time you have to set the window position in the method of componentDidMount
.
window.scrollTo(0, this.state.scrollPosition);
By default you can set the value of scrollPosition
is 0
.
Updated
Here I have used the sessionStorage
to maintain the scroll position for demo purpose. You can also use the context API or redux store to manage it.
Here is the working demo for you. https://stackblitz.com/edit/react-fystht
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
class Posts extends React.Component {
state = { posts: [] };
componentDidMount() {
axios.get("https://jsonplaceholder.typicode.com/posts").then(res => {
this.setState({ posts: res.data.slice(0, 20) }, () => {
this.handleScrollPosition();
});
});
}
// handle scroll position after content load
handleScrollPosition = () => {
const scrollPosition = sessionStorage.getItem("scrollPosition");
if (scrollPosition) {
window.scrollTo(0, parseInt(scrollPosition));
sessionStorage.removeItem("scrollPosition");
}
};
// store position in sessionStorage
handleClick = e => {
sessionStorage.setItem("scrollPosition", window.pageYOffset);
};
render() {
const { posts } = this.state;
const postsList = posts.length ? (
posts.map(post => {
return (
<div className="card" key={post.id}>
<div className="card-body">
<Link to={"/view/" + post.id} onClick={this.handleClick}>
<h5 className="card-title">{post.title}</h5>
</Link>
<p className="card-text">{post.body}</p>
</div>
</div>
);
})
) : (
<div className="text-danger text-center">No Posts yet...</div>
);
return <div>{postsList}</div>;
}
}
export default Posts;
Hope this will help you!
Upvotes: 29
Reputation: 381
Just Check Previous URL and call API:
import React, {Component} from 'react';
import axios from "axios";
import { Link } from "react-router-dom";
class About extends React.Component {
constructor() {
super();
this.state = {
posts: [],
};
}
componentDidMount() {
var url = document.referrer
if (url && !url.includes("/view/")) {
axios.get("https://jsonplaceholder.typicode.com/posts").then(res => {
this.setState({
posts: res.data.slice(0, 10),
isAboutPageOpen: true
});
});
}
}
render() {
const { posts } = this.state;
const postsList = posts.length ? (
posts.map(post => {
return (
<div className="card" key={post.id}>
<div className="card-body">
<Link to={"/view/" + post.id}>
<h5 className="card-title">{post.title}</h5>
</Link>
<p className="card-text">{post.body}</p>
</div>
</div>
);
})
) : (
<div className="text-danger text-center">No Posts yet...</div>
);
return <div>{postsList}</div>;
}
}
export default About;
Upvotes: 0