4 ms·
> event.metaKey || event.ctrlKey This misses event.shiftKey. All keyboard modifiers disqualify client-side routing. As for <Link> in its entirety, where you h
by chrismorgan 5y ago
> event.metaKey || event.ctrlKey
This misses event.shiftKey. All keyboard modifiers disqualify client-side routing.
As for <Link> in its entirety, where you have to use a special component which becomes a link and adds a click handler to it, I think this is generally the wrong solution: it’s better to instead put a single click handler on the entire document to intercept clicks on links. That way, you can just use normal links everywhere and it just works, rather than having to remember to use a whole separate component every time which may or may not be able to pass through the required properties or attributes (e.g. this one only supports class and href). Simpler, smaller and cheaper.
- wonderbore 5y ago> All keyboard modifiers disqualify client-side routing Don't forget clicks other than "left". Middle click can open a new tab.
- chrismorgan 5y agoThe middle mouse button doesn’t trigger a click event, but rather auxclick (like right click also doesn’t, but rather contextmenu and then auxclick if that’s preventDefaulted). But there are definitely situations where it’s judicious to check event.button, and I missed altKey, too. Here’s the full function Fastmail uses: const isClickModified = function (event) { return ( !!event.button || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ); };
- wonderbore 4y agoAh indeed, I probably confused it with other mouse events that do not have a button-specific event like `auxclick` and `contextmenu`. Mouse down/up only use the button to differentiate the buttons, which can also explain why that function includes such check (even if unnecessary on the `click` event) On a side note, I can't believe we still don't have a `visit` or `activate` event that works regardless of hardware, without having to exclude modifier keys.
- math-dev 5y agoWould you have an example of how to do that?
- chrismorgan 5y agoHere’s a starting point: addEventListener("click", function (event) { const link = event.target.closest("a"); if ( !event.button && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && link && link.href.startsWith(location.origin + "/") && link.target !== "_blank" ) { event.preventDefault(); navigate(link.href); } } There are some points of nuance here where you may want to vary (e.g. the determination of eligible hrefs, and handling of href targets), but this is the bones of it. You can also choose to add more functionality to it. Fastmail’s webmail, for example, uses this basic design, but also handles mailto: links specially (taking you to compose a new email), whitelists path patterns (since the domain is used for more than just the webmail app), and one or two other things.
- ReactNative22 5y agoThanks for this. Does this add an event listener to "all" clicks or only for clicks on <a href>?
- chrismorgan 5y agoIt gets called on all clicks (line 1, it’s a window-level click handler), then finds the link the click target is inside (line 2), and does nothing if it wasn’t inside a link (line 9). (You might think that it should use .closest("a[href]") instead of .closest("a"), since a:not([href]) is not a link, but in that case, link.href === "", and so it fails the line 10 is-it-eligible-for-client-side-routing test. Note also that except for the “no href attribute” case, HTMLAnchorElement#href gives a full resolved URL, so <a href=""> will produce the document base URL.) Fundamentally, there’s no such thing as an event handler that triggers only on links—you instead have to use a global event handler that starts by checking whether it’s being triggered on a link.
- delaaxe 5y agoBetter use the React root than the entire document
- chrismorgan 5y agoI don’t think the React root is really any better than the document—you should either go precise, or go global, since popstate is global. If there are some links you don’t want intercepted, they’re as likely to be within the React root as without it. See also my response to woojoo666.
- woojoo666 5y agoI think React tends to encourage local solutions (using a Link component) over global ones (inserting a global click handler) when possible. Global solutions are less composable and will cause issues if you have multiple independent teams working on the same page (eg maybe they need different click handlers)
- chrismorgan 5y agoIn general I would agree, but as soon as you’re using popstate, you’ve broken that completely: it’s inherently global and will conflict with any other compositions. Because of this, the use of a global click handler will make the application more consistent: either all forms of navigation work, or none; whereas if you use <Link> it gives the impression that you can compose, but the back and forward buttons may be broken. The History API, as it stands, is global state. That makes it one of the very few places where I say it is better to use a global solution, rather than a precise one.
- woojoo666 5y ago> In general I would agree, but as soon as you’re using popstate, you’ve broken that completely: it’s inherently global and will conflict with any other compositions It can be problematic to access globals from functional programming contexts, but I don't really see how History would cause conflicts here. Do you have any examples? If you are really worried about this then the proper way to solve it imo is to pass `history` in as a prop or Context instead of calling it from the global `window` directly. This follows functional paradigms and is more in line with React principles. This has some other advantages as well. For example, if you are using test frameworks like Jest, you can test the link component by mocking the History object being passed in.
- chrismorgan 5y agoThe URL is global state. Only one thing can use it to control what’s displayed. It is an error to manipulate `location` or `history` at the widget or library level: they must only be used at the application level. Passing history in as a property doesn’t solve anything in the application: it’s still global state, and if multiple things try to use it (even if one is trying to use the hash and the other the path), they’ll interfere with each other and you’ll have a bad time. Passing it as a property may help with mocking-based test frameworks (though I find mocking to normally be a bad technique—you get better results from unit tests and integration tests with real systems), though you may also be able to control the global variables to accomplish the same effect; yet as far as the application is concerned, it’s still global state. So then: since anything working with the URL is unavoidably working with global state, I see no virtue in avoiding a global event handler.