-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accessible Routing #19290
Accessible Routing #19290
Changes from 15 commits
164eeba
1b8221f
c525e4a
10024e1
752682e
6f52df1
81cfa5a
842830f
810b9e1
828f05f
505d0c1
2b0b6b6
4fb2972
73caa3e
ba53b68
5b15471
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,6 @@ const onPreRouteUpdate = (location, prevLocation) => { | |
const onRouteUpdate = (location, prevLocation) => { | ||
if (!maybeRedirect(location.pathname)) { | ||
apiRunner(`onRouteUpdate`, { location, prevLocation }) | ||
|
||
// Temp hack while awaiting https://github.com/reach/router/issues/119 | ||
window.__navigatingToLink = false | ||
} | ||
|
@@ -159,6 +158,55 @@ function init() { | |
maybeRedirect(window.location.pathname) | ||
} | ||
|
||
class RouteAnnouncer extends React.Component { | ||
constructor(props) { | ||
super(props) | ||
this.announcementRef = React.createRef() | ||
} | ||
|
||
componentDidUpdate(prevProps, nextProps) { | ||
requestAnimationFrame(() => { | ||
let pageName = `new page at ${this.props.location.pathname}` | ||
if (document.title) { | ||
pageName = document.title | ||
} | ||
const pageHeadings = document | ||
.getElementById(`gatsby-focus-wrapper`) | ||
.getElementsByTagName(`h1`) | ||
if (pageHeadings && pageHeadings.length) { | ||
pageName = pageHeadings[0].textContent | ||
} | ||
const newAnnouncement = `Navigated to ${pageName}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about internationalization support? |
||
const oldAnnouncement = this.announcementRef.current.innerText | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, one thing I just thought of. We should add a bailout check if the ref isn't attached yet. It feels unlikely to occur, but technically ref's type is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @blainekasten Suspense won’t change anything here. It’s impossible for the ref to be null during componentDidUpdate, though note that because of the rAF call it is possible (though probably unlikely) for the component to unmount before this code runs, so a check is appropriate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @blainekasten I think this check is still missing in the code. Just talked to someone on Discord getting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm getting this error while trying to debug problems with Ms Edge (14). Happens when I try to navigate to a new page. |
||
if (oldAnnouncement !== newAnnouncement) { | ||
this.announcementRef.current.innerText = newAnnouncement | ||
} | ||
}) | ||
} | ||
|
||
render() { | ||
return ( | ||
<div | ||
id="gatsby-announcer" | ||
style={{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please extract object to const outside of class scope, or React.useMemo hook There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a very minor perf issue. Creating one object on re-renders (which only happen on route change). It's probably easier to just implement a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Easier - does not mean best way. Counting that react moving to functions, instead of components, and original advise do not use shouldComponentUpdate, this style object already a symptom that there could be much more performance issues in gatsby.js project itself. A lot of very minor issues adds up pretty darn fast There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Former React team manager here – I can guarantee this will never be a performance problem and there is nothing wrong with writing the style object like this. I’d recommend you stop worrying about microoptimizing code. |
||
position: `absolute`, | ||
width: 1, | ||
height: 1, | ||
padding: 0, | ||
overflow: `hidden`, | ||
clip: `rect(0, 0, 0, 0)`, | ||
whiteSpace: `nowrap`, | ||
border: 0, | ||
}} | ||
role="alert" | ||
aria-live="assertive" | ||
aria-atomic="true" | ||
ref={this.announcementRef} | ||
></div> | ||
) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this component doesnt render any dynamic data, I wonder if we could add a shouldComponentUpdate() { return false; } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is still getting used to using refs like this does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nope. On initial render, the ref will be passed to React, who will attach it to the node. It happens asynchronous and does not affect component lifecycles. Changing the ref will not cause any re-renders ever! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay I'm trying to leverage my new-ness to React as something useful and ask my potentially trivial questions out in the open 🙈 Our changes happen to the announcement ref happen within the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great questions! And you know what, I'm wrong. 😆 Since you are doing it in DidUpdate, that occurs after a render. So stopping update stops that cycle. Let's just ship this, I think there are some changes that would better align with React, but this works. So let's ship and we can see if there are better iterations later :) |
||
|
||
// Fire on(Pre)RouteUpdate APIs | ||
class RouteUpdates extends React.Component { | ||
constructor(props) { | ||
|
@@ -186,7 +234,12 @@ class RouteUpdates extends React.Component { | |
} | ||
|
||
render() { | ||
return this.props.children | ||
return ( | ||
<React.Fragment> | ||
{this.props.children} | ||
<RouteAnnouncer location={location} /> | ||
</React.Fragment> | ||
) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,15 +52,19 @@ const RouteHandler = props => ( | |
</BaseContext.Provider> | ||
) | ||
|
||
let locationListener | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the purpose of this? I can't see how it's used at the moment |
||
class LocationHandler extends React.Component { | ||
render() { | ||
let { location } = this.props | ||
const { location } = this.props | ||
|
||
if (!loader.isPageNotFound(location.pathname)) { | ||
return ( | ||
<EnsureResources location={location}> | ||
{locationAndPageResources => ( | ||
<RouteUpdates location={location}> | ||
<RouteUpdates | ||
location={location} | ||
locationListener={locationListener} | ||
> | ||
<ScrollContext | ||
location={location} | ||
shouldUpdateScroll={shouldUpdateScroll} | ||
|
@@ -96,7 +100,7 @@ class LocationHandler extends React.Component { | |
} | ||
|
||
return ( | ||
<RouteUpdates location={location}> | ||
<RouteUpdates location={location} locationListener={locationListener}> | ||
<Router | ||
basepath={__BASE_PATH__} | ||
location={location} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ES6 tip (feel free to do this if you want). But you can ditch the constructor here so we're just instantiating a class variable. Do it like this instead:
It'll achieve the same result! (if this doesn't work ignore, it does require a specific babel setup to make it work and maybe we don't have that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm going to leave this for a later iteration. I'm ready to get this thing OUT!