1

After reading many questions regarding this topic I am still unsure as to which is the best way to asynchronously fetch data which later will be passed down as props to the child routes with React Router v1.0.0 and up.

My route config looks something like this:

import { render } from 'react-dom';
// more imports ...

...

render(
  <Router>
  <Route path="/" component={App} />
    <IndexRoute component={Dashboard}/>
    <Route path="userpanel" component={UserPanel}/>
  </Router>,
  document.getElementById('container')
)

In my App component I have code which asynchronously fetches data from the backend and will incorporate it into its state, if fetching was successful. I use componentDidMount for this within App.

The state of App will look like this contrived example:

{
  user: {
    name: 'Mike Smith',
    email: 'mike@smith.com'
  }
}

I would want to pass the user part of state as props to my IndexRoute and the userpanel route. However I am not sure how I should do this.

A few questions come to mind:

  1. Should I place the async data request somewhere else within my code?
  2. Should I use the React Router api (like onEnter) instead of React lifecycle methods for the data fetching?
  3. How can I pass the state (user) of App to the Dashboard and UserPanel components as props?
  4. I am unsure how to do this with React.cloneElement as seen in other answers.

Thanks for the help in advance.

3 Answers3

0

You can do one thing when your application start at that time you will call the API and fetch the data and register your Route like my index.js is entry file then

here I have used React-Router 0.13.3 you can change the syntax as per new Router

fetchData(config.url+'/Tasks.json?TenantId='+config.TenantId).then(function(items)
{
    var TaskData=JSON.parse(JSON.stringify(items.json.Tasks));
    var Data=[];
    Object.keys(TaskData).map(function(task){
        if(TaskData[task].PageName !=='' && TaskData[task].PageUrl !=='')
        {
            Data.push({PageName:TaskData[task].PageName,path:TaskData[task].PageName+'/?:RelationId',PageUrl:TaskData[task].PageUrl});
        }
    });

    Data.push({PageName:'ContainerPage',path:'/ContainerPage/?:RelationId',PageUrl:'./pages/ContainerPage'});

    var routes=require('./routes')(Data);
    $("#root").empty();
    Router.run(routes,function(Handler){
        React.render(<Handler />,document.getElementById('root'));
    });

    React.render(<UserRoles />, document.getElementById("userrole"));
}).catch(function(response)
{
    showError(response);
});

I have pass the data to routes.js file like var routes=require('./routes')(Data); and my routes.js file look like

export default (data =>
  <Route name="App" path="/" handler={App}>
    <NotFoundRoute handler={require('./pages/PageNotFound')} />
    <Route handler={TaskList} data={data} >
    </Route>
{ data.map(task =>
  <Route name={task.PageName} path={task.path}  handler={require(task.PageUrl)}>
  </Route>
    ) }
</Route>
);
Dhaval Patel
  • 7,471
  • 6
  • 37
  • 70
0

What you are asking for is persistent data between routes and that's not the job of the router.

You should create a store (in flux terms), or a model/collection (in MVC terms) - the usual approach with react is something flux-like. I recommend redux.

In the redux docs it has an example of fetching a reddit user:

componentDidMount() {
  const { dispatch, selectedReddit } = this.props
  dispatch(fetchPostsIfNeeded(selectedReddit))
}

Personally I don't think flux/redux is the easiest approach to implement, but it scales well. The essential concept is even if you decide to use something else:

  • You are correct, as Facebook suggests, async fetching goes best in componentDidMount.

If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.

  • Next you need to set this data in a store/model which can be accessed from other components.

The nice thing about redux (with react-redux) is that for each component you can say "Here are the actions this component is interested in" and then that component can simply call the action like UserActions.fetchUserIfNeeded() and the action will figure out whether it already has the user or if it should be fetched, and afterwards it will re-render and the prop will be available.

Answer to Q4: What are you trying to clone and why? If it's a child see this answer.

Community
  • 1
  • 1
Dominic
  • 62,658
  • 20
  • 139
  • 163
  • Thanks you for the thorough explanation, I will give it a try. However it seems to me as if the router doesn't allow for one of the most essential react features in the form of one way data bindings, by means of props. Or am I missing something? – slowdownitsfine Jan 06 '16 at 14:11
  • The problem is that props are top down, so if something down one route makes a request and gets some data, you can't then pass it as a prop for the next route, unless you store it somewhere. You can in theory pass your store or some data from your store (or model) to the router though (as of v1) https://github.com/rackt/react-router/issues/1159#issuecomment-113186023 – Dominic Jan 06 '16 at 14:26
0

I am not entirely sure I understand the question, but I just recently passed properties to the children of my routes as well. Pardon me if this is not the best way of doing it, but you'll have to clone your children and edit them and then pass down the copies not the children. I'm not sure why react and the react router make you do this, but try this:
let children (or whatever you want to name it) = React.Children.map(this.props.children, (child) => { return React.cloneElement(child, {name of property: property value}); }); Afterwards, you should be able to access those properties in this.props in the sub routes. Please ask if you have any questions because this is pretty confusing.

Tim Gass
  • 383
  • 3
  • 9