React-router: Export context to allow history access

Created on 1 Oct 2018  路  13Comments  路  Source: ReactTraining/react-router

Ref https://github.com/ReactTraining/react-router/issues/6362#issuecomment-425841471

Since "always render" behaviour will be removed from <Route children /> it would be good to provide a way to access context values from any part of app.

Here's a few ways and all requires to provide RouterContext.

RouterContext.Consumer

const Component = () => (
  <RouterContext.Consumer>
    {context => (
      <button onClick={() => {
        if (context) {
          context.history.push('/users')
        }
      }}>Open users</button>
    )}
  </RouterContext.Consumer>
)

Class component contextType

const Component = class extends React.Component {
  contextType = RouterContext;
  handler = () => {
    this.context.history.push('/users')
  }
}

context.unstable_read api

const Component = () => {
  const context = RouterContext.unstable_read();
  return (
    <button onClick={() => {
      if (context) {
        context.history.push('/users')
      }
    }}>Open users</button>
  )
}

All 13 comments

I think you may be confusing 2 ideas. I'm proposing that we stop rendering something with a <Route> when it doesn't match. But it's still possible to create a <Route> that always matches. You can just use path="*" or no path at all:

<Route path="*" children={({ history }) => (
  <button onClick={() => history.push('/users')}>Go</button>
</Route>

// or even

<Route children={({ history }) => (
  <button onClick={() => history.push('/users')}>Go</button>
</Route>

In fact, we use a <Route> with no path in the implementation of withRouter, so you can use that too if you prefer to use a higher-order component instead.

BTW I'm a pretty strong -1 about exposing context as public API. That's our internal API. Our public API is <Route> and withRouter. You can get anything you need using them.

BTW, if it helps, I'm getting the strong sense that unstable_read is going away in favor of contextType (no "s"!): facebook/react#13728

@timdorr I use many context in my app. contextType allows to achieve only one. This sucks. unstable_read is pretty useful for translations.

import { t } from '../locale.js';
// localeContext.unstable_read().t under the hook
export const Component = () => (
  t('user')
)

@timdorr unstable_read is used for suspended queries. I don't think it will be removed in favour of contextType.
https://github.com/facebook/react/pull/13337#issuecomment-425974864

@mjackson AFAIK exporting context is going to be normal/expected with what's coming. Formik is going to do the same.

Just a reminder: You will always be able to get to it via a direct import (import RouterContext from 'react-router/es/RouterContext'). So if you want to do some wonky stuff, you're free to aim the gun at your foot. This is just about making it a top-level export, which I agree with @mjackson isn't really necessary.

@jaredpalmer It just seems like context shouldn't be public. It's just a communication channel that we use to communicate between our components since we can't pass props down at every level.

I think the goal should be to eventually drop withRouter() and only use RouterContext.

Edit: To elaborate, I think of a context consumer (read from) as being public, while a provider (write to) is private. A <Route> is a bit unusual since it is both a consumer and a provider. A pathless <Route> (outside of a <Switch>) is only being used as a consumer, so it is just adding complexity when a user really just wants to read from the context.

Exposing the consumer will also let users use whatever future context optimizations for class components there are (Context.read(), static contextType, etc.). The current pattern of using a wrapper around classes that need access to the context in lifecycle methods is tedious at best.

While I agree with @mjackson that we shouldn't access the private context, I have a argument to why context would be better.

As we know withRouter just wraps your component in a path less route giving your component access to all the props, mainly match and history in my use case.

The downside to withRouter and the HOC is that the react dev tools show a bunch of routes everywhere.

Getting access to the context Context.Consumer? We would be able to pluck off the props we want without the HOC.

Am I totally off here or is there a secret way to gain access to router props without wrapping your component in a route.

The fact that we use context under the hood is just an implementation detail!

Take the 4.4 release, for example. If context were part of our public API, changing from the old context to the new one would have been a breaking change, and we would've had a major version bump. But since it's not, we are free to refactor and move things around while still providing the same high-level API. That's the beautiful part about us not exposing context.

Also, what if the context implementation changes again (and it could)? If we expose it as public API, we will have the same problem.

Just to address the examples in the OP:

For the first example, <Route> is basically the same as the Consumer. So, this is equivalent:

const Component = () => (
  <Route>
    {({ history }) => (
      <button onClick={() => {
        if (history) {
          history.push('/users')
        }
      }}>Open users</button>
    )}
  </Route>
)

For the second example and in preparation of facebook/react#13728 landing, we could set a withRouter'ed component's contextType to support this.context usage without the side effects. But we would have this.props available, which would be a more ideal option. I'd rewrite this as:

class Component extends React.Component {
  handler = () => {
    this.props.history.push('/users')
  }
}
export default withRouter(Component)

And for the last example, I'm not sure if unstable_read is going to survive much longer now that contextType exists.

The React team definitely wants to discourage context ~abuse~ usage, so reliance on it as an official API for a library would seem to be counter to those goals. I'm aligned with @mjackson's feelings on the subject.

I'd say we can go ahead and close this for now since neither I nor @timdorr see any reason why you should need direct access to context. Happy to reopen if this changes in the future.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

winkler1 picture winkler1  路  3Comments

davetgreen picture davetgreen  路  3Comments

nicolashery picture nicolashery  路  3Comments

ryansobol picture ryansobol  路  3Comments

maier-stefan picture maier-stefan  路  3Comments