Reputation: 781
I am currently using react to create a Wikipedia style website. For data entry reasons, I am fetching an entire HTML from the database and then using dangerouslySetInnerHTML to set some part of it, like so:
dangerouslySetInnerHTML={{ __html: this.props.section.text }}
Now there are other parts of the page that have a video playing in it, however every time I click on an <a href="#id>
style tag (set using the database) to navigate to another part of the same page, the entire page refreshes.
This creates a problem since the video reloads as well and starts playing from the beginning.
Is there any way to use a dangerously set anchor tag to scroll to parts of a page in React without full page reload?
Edit: Using following versions:
"react": "^16.0.0", "react-router-dom": "^4.2.2"
Upvotes: 3
Views: 14923
Reputation: 3813
To add onto what @Shishir Arora was saying, instead of using history.pushState({},'',href);
use something like document.getElementById(hash.replace('#', '')).scrollIntoView();
.
Here's code for a full component where I leverage jQuery:
import React, { useEffect } from 'react';
import $ from 'jquery';
const handleAnchorClick = e => {
e.preventDefault();
e.stopPropagation();
const hash = e.currentTarget.hash;
document.getElementById(hash.replace('#', '')).scrollIntoView();
// Alternatively use jQuery for the above line: $(hash).get(0).scrollIntoView();
};
export default ({ response, url, search }) => {
useEffect(() => {
$('#article-content').on('click', 'a[href^="#"]', handleAnchorClick);
return () => {
$('#article-content').off('click', 'a[href^="#"]', handleAnchorClick);
};
}, [response]);
return (
<div>
<div id='article-content' dangerouslySetInnerHTML={{__html: response}} />
</div>
);
};
jQuery was helpful in capturing the currentTarget
which won't get captured the same way with @Shishir's original answer because of an element nested within the tag.
Upvotes: 0
Reputation: 5923
catch default eventhandler and handle event handling yourself. you can put this in componentDidMount lifecycle hook. Also remove these handlers in componentWillUnmount
var aTags = document.querySelectorAll('a[href]'); //use apt selector for you section
aTags.forEach(aTag =>aTag.addEventListener('click',function(e){
e.preventDefault();
e.nativeEvent.stopImmediatePropagation()
e.stopPropagation()
var href = e.target.href;
history.pushState({},'',href);
}));
Upvotes: 1
Reputation: 59511
What you could do is to add an eventListener to your component which listens to all anchor tags whose href
attribute value start with #
.
Inside your componentDidMount()
method, select all your <a>
tags where href="#..."
. You can do this with document.querySelectorAll("a[href^='#']")
. That will return an array of all nodes that were matched. You can then loop through them and attach an eventListener to each one.
The eventListener would listen to click
and run a function which prevents the default behavior (redirect to another page) and instead push to your router. (Remove the comment below)
class MyApp extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
document.querySelectorAll("a[href^='#']").forEach(node => {
node.addEventListener('click', e => {
e.preventDefault();
console.log(e.target.href);
//this.props.history.push(e.target.href);
});
})
}
render() {
const myMarkup = "<ul><li><a href='#'>Page link 1</a></li><li><a href='#test'>Page link 2</a></li><li><a href='https://google.com'>External link</a></li></ul>";
return(
<div id="container" dangerouslySetInnerHTML={{__html: myMarkup}}></div>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
The above snippet assumes you are using the withRouter
HOC. Otherwise the push won't work. For more info, see my answer to How to push to History in React Router v4?.
Upvotes: 3
Reputation: 1748
Since you're using React Router v4, why don't you use Link
?
...
import { Link } from 'react-router-dom';
...
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: { fromDashboard: true }
}}/>
You can find the documentation here.
Upvotes: -2
Reputation: 3336
Since your question has the tag "react-router" I assume you use it in your application, I also assume you are using the latest version which v4.x.
The way I have tackled this in the past was using React Router Hash link package. As is noted in their documentation, you can use it like that (replace your <a href="#id">
with <Link>
):
// In YourComponent.js
...
import { HashLink as Link } from 'react-router-hash-link';
...
// Use it just like a RRv4 link (to can be a string or an object, see RRv4 api for details):
<Link to="/some/path#with-hash-fragment">Link to Hash Fragment</Link>
Upvotes: -1