6

I can't figure out how to pass props to the component. This is important as I don't want to fetch data in the componentDidMount method as it will then be invisible to search engines.

My code looks like this

const router = 
<Route path="/" component={App}>
<IndexRoute onEnter={redirectToLogin} component={LandingPage} />
<Route path="panel" component={ ControlPanel }>
  ...
</Route>
<Route 
  path="/:handle" 
  onEnter={maybeRedirectToHome}
  getComponent={(location, callback)=> {
      getSiteHandleByName(location.pathname.slice(1))
      .then(function(handle){
        if (handle){
          callback(null, Portfolio)
        } else {
          callback(null, NotFound)
        }
      })
      .catch(callback)
  }}
  getChildRoutes={(location, callback)=> {
    callback(null, portfolioRoutes)
  }} 
/>
</Route>

I'm trying to serve up a portfolio React App when the user visits a valid url like mysite.com/thishandleisvalid but I'll need to also fetch all the content for that app at the getComponent point and pass it in as a property. E.g. you might normally do something like this <Portfolio contentItems={fetchedItems} />.

Is doing this possible?

Ally
  • 4,894
  • 8
  • 37
  • 45

3 Answers3

16

This is really easy to do with stateless components. Just do something like:

function getComponent(location, callback) {
  const Component = /* ... */;
  const items = /* ... */;

  callback(null, props => <Component {...props} items={items} />);
}

The reason we don't explicitly support this sort of pattern is because it's fairly atypical to wire things up this way - for apps that need to deal with this sort of thing, it's much more common to use the onEnter hook to e.g. populate a Flux store, then connect the components as appropriate to the relevant stores.

taion
  • 2,827
  • 1
  • 14
  • 22
  • This works great thanks. And thank you for the info about using the onEnter hook and a flux store. That's still an area I find confusing and need to learn about. – Ally Nov 07 '15 at 16:55
  • This works well for me as I'm trying to only pass actions to a component on a per-route basis without polluting the props across the application, say if I used 'React.cloneElement(this.props.children, {'actions': fooActions})' at the app root. – mmmpop Jan 14 '16 at 22:18
5

Also if you don't mind accessing props under route, you can pass props like this:

JSX:

<Route 
  path="route_path" 
  customProp="hello"
  getComponent={(location, cb) => {
    // ...
  }}

Programatically:

childRoutes: [
{
  path: 'route_path',
  customProps: 'hello',
  getComponent(location, cb) {
    // ....
  },

And customProp will be available via props.route.customProp

Dominic
  • 62,658
  • 20
  • 139
  • 163
0

currying looks as a simpler way (and even more simpler with recompose/withProps)

const getComponentWithProps = (props) =>
  (location, cb) => {
    const Component = /* ... */;
    // ...
    cb(null, withProps(props)(Component))
  }
...
childRoutes: [
  {
    path: 'route_path',
    getComponent: getComponentWithProps({ foo: 'bar' }),
  }
]

UPDATE

CAUTION: PERFORMANCE my version is just a sugaring over taion's answer, and, i suppose, has the same troubles: it causes complete component rerender on rerouting.

it happens because of rewrapping component with withProps enhancer on each new location, that generates new component on each rerouting.

as a quick solution i decided to cache all enhancings in weakmaps. in my case it looks like

const getOrAdd = (map) =>
  (value = Function.prototype) =>
    (key) =>
      map.get(key)
        || (
          map.set(
            key,
            value(),
          )
            && map.get(key)
        );

const componentsMap = new WeakMap();
export const getEnhancedMap = getOrAdd(componentsMap)(() => new WeakMap());

export const getEnhancedComponent = (
  component,
  enhancer,
) => {
  const enhancedMap = getEnhancedMap(component);
  return getOrAdd(enhancedMap)(() => enhancer(component))(enhancer);
}
newvar
  • 1
  • 1