Reputation: 762
How can I stop a nested button triggering the parent container Link using stopPropagation? I've checked the other question/answers but I still can't stop it happening. I have the following setup:
{feedData && feedData.map(post => (
<Link to={`/buy/${post.name}`} key={post.id}>
<Card />
</Link>
))}
The Card pulls in an Upvote component with a button inside it:
const Card = ({id}) => {
return (
<div>
...
<Upvote id={id}/>
</div>
)
}
The Upvote component is basically a button:
<button onClick={(e) => e.stopPropagation() | unlike()}>
<span>{likes}</span>
</button>
Can anyone see what I'm doing wrong, please?
Upvotes: 2
Views: 1740
Reputation: 21161
You can see in the console.log
in the example below how the click event is already not reaching the <a>
element due to e.stopPropagation()
, but the default behavior would still trigger unless you also use e.preventDefault()
:
const App = () => {
const handleLinkClick = React.useCallback((e) => {
console.log('Link Click');
}, []);
const handleButtonClick = React.useCallback((e) => {
e.stopPropagation();
// This is what you are missing:
e.preventDefault();
console.log('Button Click');
}, []);
return (<React.Fragment>
<a href="https//www.google.com" target="_blank" onClick={ handleLinkClick }>
<div>I'm a link!</div>
<button onClick={ handleButtonClick }>I'm a button!</button>
</a>
</React.Fragment>);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
a {
display: block;
border: 2px solid black;
padding: 8px;
margin: 8px;
}
a:hover {
background: yellow;
}
button {
margin: 16px 0 0;
padding: 8px;
border: 2px solid black;
background: white;
cursor: pointer;
border-radius: 2px;
}
button:hover {
background: cyan;
}
.as-console-wrapper {
max-height: 67px !important;
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
In any case, that's invalid HTML, so you might want to consider a different solution:
You can overlay an empty <a>
element on top of all the other content inside <Card>
and make sure the interactive bits like buttons or nested links are drawn on top using z-index
. Also, there's no need for e.stopPropagation()
or e.preventDefault()
, but you might want to add aria-label
to describe what it does:
const App = () => {
const handleLinkClick = React.useCallback((e) => {
console.log('Link Click');
}, []);
const handleButtonClick = React.useCallback((e) => {
// No need for these now:
// e.stopPropagation();
// e.preventDefault();
console.log('Button Click');
}, []);
return (<div className="item">
<div>I'm a link!</div>
<button onClick={ handleButtonClick }>I'm a button!</button>
<a href="https//www.google.com" target="_blank" onClick={ handleLinkClick } aria-label="Open Google"></a>
</div>);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
.item {
display: block;
border: 2px solid black;
padding: 8px;
margin: 8px;
position: relative;
}
.item:hover {
background: yellow;
}
button {
margin: 16px 0 0;
padding: 8px;
border: 2px solid black;
background: white;
cursor: pointer;
border-radius: 2px;
position: relative;
z-index: 1;
}
button:hover {
background: cyan;
}
a {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.as-console-wrapper {
max-height: 67px !important;
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
Upvotes: 4