18

After updating to React 16.9.0 I'm getting big warnings like this:

Warning: A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed "javascript:;".

It comes from code like this:

const Component = ({someAction}) => (
  <a href="javascript:void(0)" onClick={someAction}>click me</a>
);

Looking at the StackOverflow question about which “href” value should I use for JavaScript links in HTML it seems most of you agree javascript:void(0) is the best option, which will be no longer possible in React 16.9.0.

Just replacing with href="#" is problematic, since the browser will scroll to the top of the page and change the displayed URL. Especially if you use hash-links for routing this is very problematic.

I could update my whole codebase to have e.preventDefault(); in each and every event handler, but this seems hard to do, especially when the event handlers are automatically created from Redux action creators or hooks. I do not look for the answer "just include e.preventDefault();" everywhere!

Also using a <button> means I have to deal with lots of unwanted styles applied.

So I was wondering: Are there any solutions specific to the library React to get a working <a> link that just triggers an action without side effects? I want to change the code as little as possible and get rid of deprecation warnings.

amoebe
  • 4,857
  • 5
  • 37
  • 42
  • @EmileBergeron But I want to avoid calling `e.preventDefault()` manually all the time. – amoebe Aug 15 '19 at 17:56
  • 2
    If you have that much links that are buttons, I'd go the button way. You can even use a `div` though that would need some accessibility tweaks. – Emile Bergeron Aug 15 '19 at 18:00
  • 5
    If it's a link that never functions as a link, it should be a button. – lonesomeday Aug 15 '19 at 18:01
  • 1
    I mean there are some answers [about making a button look like a link](https://stackoverflow.com/q/1367409/935676) but this seems really complicated and involves some arcane browser-specific CSS. – amoebe Aug 15 '19 at 18:04
  • These answers are old, really old web-wise. Most browsers now don't need all these tricks. You should definitely make sure it works in all the browsers you want to support. – Emile Bergeron Aug 15 '19 at 18:09
  • You could use some CSS framework like [Bootstrap as a reference](https://github.com/twbs/bootstrap/blob/4b243de2c0af9325516cd435bd7e9cd4f6da313a/scss/_buttons.scss#L77-L100), or just use a CSS framework if you're unsure how to apply safe cross-browser styling. – Emile Bergeron Aug 15 '19 at 18:12
  • @EmileBergeron Sorry, but your bootstrap reference is really not helpful, since it uses a whole cascade of styles e.g. from their reboot CSS and also some extra paddings. Same with "You should definitely make sure it works in all the browsers", this is not the drop-in replacement I want. – amoebe Aug 15 '19 at 18:18
  • 1
    Yes, don't use anchors as buttons. It's bad practice for semantic markup and accessibility. – isherwood Aug 15 '19 at 18:19
  • What I meant, is if you're unsure how, something that's up to date and proven to work is a good reference. Old answers on Stack Overflow won't really help. – Emile Bergeron Aug 15 '19 at 18:22
  • Yeah, I'm looking for something like that. – amoebe Aug 15 '19 at 18:28
  • 3
    There are already people doubting the decision of the react team to add this warning: https://github.com/facebook/react/pull/15047#issuecomment-521736797 – amoebe Aug 15 '19 at 18:32

2 Answers2

24

In React blog post:

URLs starting with javascript: are a dangerous attack surface because it’s easy to accidentally include unsanitized output in a tag like <a href> and create a security hole.

In React 16.9, this pattern continues to work, but it will log a warning. If you use javascript: URLs for logic, try to use React event handlers instead.

I personally prefer to use it like this: <a href="#!" onClick={clickAction}>Link</a>

N'Bayramberdiyev
  • 5,936
  • 7
  • 27
  • 47
  • 2
    I have read the blog post. This still leaves me with the problem of calling `e.preventDefault()` somehow. – amoebe Aug 15 '19 at 18:22
4

I came up with a component like this:

import React, {useCallback} from 'react';

function AHrefJavascript({ children, onClick, ...props }) {
  const handleClick = useCallback(
    e => {
      e.preventDefault();
      return onClick(e);
    },
    [onClick]
  );

  return (
    <a href="#javascript" {...props} onClick={handleClick}>
      {children}
    </a>
  );
}

It will just wrap the event handler to create a new one that also calls e.preventDefault(). It will add a hash link to href which does not trigger the deprecation warning. Using the power of hooks it only changes the event handler, when the passed in handler is updated. One problem with this that in the browser you can still open this link in a new tab, so it is not quite as good as a link with a javascript:void(0), which would prevent that.

It is easy to use, the component looks like this:

const Component = ({someAction}) => (
  <AHrefJavascript onClick={someAction}>click me</a>
);

So probably one could replace a href="javascript:void(0)" with AHrefJavascript across the project and then just add an import for the component everywhere.

In the end maybe the best option is to use a <button> and go through the hoops to remove all the unwanted styles.

amoebe
  • 4,857
  • 5
  • 37
  • 42
  • 1
    A component just to call `e.preventDefault()` seems overkill, and sometimes, you'll want to keep the default behaviour as well. Though it's a good idea to use some kind of `Link` component to manage different behaviours, like tracking, routing, styling, etc. – Emile Bergeron Aug 15 '19 at 17:57