Skip to content

Instantly share code, notes, and snippets.

@taion
Last active January 24, 2017 17:14
Show Gist options
  • Save taion/66b6ce17b601568d7ad3321e3dda8b8d to your computer and use it in GitHub Desktop.
Save taion/66b6ce17b601568d7ad3321e3dda8b8d to your computer and use it in GitHub Desktop.
React Router data fetching
// Define your components like:
class MyComponent extends React.Component {
static fetchData = (params) => {
// return an action here.
};
/* ... */
}
function fetchComponentData(component, store, params) {
if (component.fetchData) {
store.dispatch(component.fetchData(params));
};
}
export default store => ({
createRouterContext: (child, { components, params }) => {
for (const component of components) {
if (!component) {
continue;
}
if (typeof component === 'object') {
Object.keys(component).forEach(key => {
fetchComponentData(component[key], store, params);
});
continue;
}
fetchComponentData(component, store, params);
}
return child;
},
});
// Define your routes like:
const myRoute = (
<Route
component={myComponent}
fetchData={params => /* action */}
/>
);
export default store => ({
createRouterContext: (child, { routes, params }) => {
routes
.map(route => route.fetchData)
.filter(fetchData => fetchData)
.forEach(fetchData => store.dispatch(fetchData(params)));
return child;
},
});
ReactDOM.render(
<Router
history={history}
routes={routes}
render={applyRouterMiddleware(useFetchData(store))}
/>,
container
);
@esmevane
Copy link

@kilianc, I just played with this example and came up with this:

class DispatcherContext extends React.Component {
  componentWillMount() {
    const dispatch = component => {
      const { store, params } = this.props

      if (component.fetchData) {
        store.dispatch(component.fetchData(params))
      }
    }

    for (const component of this.props.components) {
      if (!component) { continue }

      if (typeof component === 'object') {
        Object.keys(component).forEach(key => dispatch(component[key]))
      }

      dispatch(component)
    }
  }

  render() { return this.props.children }
}

const dispatcherContext = store => {
  const renderRouterContext = (child, props) =>
    <DispatcherContext store={ store } { ...props }>
      { child }
    </DispatcherContext>

  return { renderRouterContext }
}
<Router history={ history }
        routes={ Routes }
        render={ applyRouterMiddleware(dispatcherContext(store)) } />

Which works with the component definition he showed at the top of this gist.

@esmevane
Copy link

Discovered a bug with the above. Here's a more complete resolution:

import React from 'react'

class DispatcherContext extends React.Component {
  _fetchData() {
    const { action, dispatch } = this.props

    dispatch(action())
  }

  // We trigger a fetchData call the first time we're loaded, every time.
  // 
  componentDidMount() { this._fetchData() }

  // If this component is passed different children at any point, we want to
  // trigger another fetchData call.
  // 
  // Just putting a blanket update here tends to lead to infinite loops.
  // 
  componentDidUpdate(previous) {
    const previousName = previous.children.type.displayName
    const currentName = this.props.children.type.displayName

    if (previousName !== currentName) { this._fetchData() }
  }

  // We're a bare wrapper around a component with fetchable actions.
  // 
  render() { return this.props.children }
}

export const dispatcherContext = store => {
  // We want to wrap every component with a fetchData static property. I.E.,
  // fetchables.
  // 
  const renderRouteComponent = child => {
    if (child.type.fetchData) {
      return(
        <DispatcherContext dispatch={ store.dispatch }
                           action={ child.type.fetchData }>
          { child }
        </DispatcherContext>
      )
    }

    return child
  }

  return { renderRouteComponent }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment