Fetching data in Flux applications has always been a controversial point. Should it be handled inside the stores? Should we have asynchronous actions to get it?
In the last React conference we knew how Facebook fetches the data in their react applications. Basically they created a language to compose fetch queries called GraphQL. Their servers understand GraphQL queries and return just the requested data.
It is really interesting how they create the GraphQL queries. Every component define their data dependencies declaratively. Something like this:
var FriendInfo = React.createClass({ statics: { queries: { user: function(){ return graphql `User { name, mutual_friends { count } }`; } } }, render: function() { ... } });
Before the component is mounted, facebook app fetches the data that it needs and inject it as props, so FriendInfo
can access to the user data just accessing to this.props.user
. This is done automatically thanks to a framework that they created called Relay, no special development (actions, actionHandlers... ) is needed when creating a new component to fetch the needed data.
GraphQL, Relay... sounds really complex, and they are not opensourced yet. Is it possible to have the same workflow nowadays with our current tools? Sure, let's make a small app that works this way:
What do we want? We want to define data dependencies for the components declaratively and inside the components themselves, the same way that is done with Facebook's Relay.
Imagine we have a Posts
component that lists all the posts in our blog. We need to fetch all the posts of the blog for our component, so it will be like this:
var Posts = React.createClass({ statics: { deps: function(){ return { posts: '/posts' }; } }, render: function() { return ( <div className="postsPage"> <h2>Posts</h2> <Table rows={ this.props.posts } /> </div> ); } }
We have defined that the component will need the data from the API route /posts
. That data should be fetched automatically, so the component can access to it through this.props.posts
.
This declaration is inside a static method in the react component because it will be called before the component is mounted, Even before it is instantiated, so it is not possible to call this
inside the method.
Since we don't have a GraphQL server, we are going to use a REST API. You probably would prefer to create an abstraction for fetching your data models, and not to use URLs inside the components, but for the sake of the demo I will leave it like this, keeping it easy to understand.
In every step, I will explain the tools I have used to make my development easier:
- jsonplaceholder let us to build our test REST service in just 30 seconds.
- react-json-table will format the post list for us.
Reading component's dependencies Declaring the dependencies is easy, but we need to read and fetch them before mounting the component. That's sounds much harder. For doing so our app will use react-router, but it is possible to follow the same strategy using other wrappers.
The app will be navigable, every time the user navigates the URL will change and react-router will load a different component for every URL. When the router is loading the component, it is the perfect time to read, fetch and inject the dependencies.
We will have routes to manage posts and users:
var routes = ( <Route name="app" path="/" handler={ App } location="history" > <DefaultRoute name="home" handler={ Home } /> <Route name="posts" path="/posts" handler={ Posts } /> <Route name="post" path="/post/:id" handler={ Post } /> <Route name="users" path="/users" handler={ Users } /> <Route name="user" path="/user/:id" handler={ User } /> <NotFoundRoute name="notfound" handler={ Home } /> </Route> );
In case you are not familiar with react-router, the code above defines the routes for our application. The main route /
is handled by the App
component, and inside it, other components will be loaded depending on the current URL. For the route /posts
the component Post,
that we defined before, will be loaded inside App
. Let's have a look at the App
's render
method to understand how it works:
var App = React.createClass({ contextTypes: { // Router available through this.context.router router: React.PropTypes.func }, render: function() { return ( <div> <Header /> <div className="content wrapper"> <RouteHandler /> </div> </div> ); } }
RouteHandler
is a component provided by react-router. It will display the component for the route that matches the current URL, if we visit the route /posts
it will print the Posts
component out, or if we are in /users
it will show the Users
component.
Our tasks are clear in order to load the dependencies:
- Detect URL changes.
- Know what is the component that
RouteHandler
is going to show and check its dependencies. - Fetch their dependencies and inject them into the component.
App
component to control this process:
var App = React.createClass({ getInitialState: function(){ return { // Last URL loaded currentPath: false, // Whenever it is fetching dependencies loadingDeps: false, // Fetched data for the current route component handlerDeps: {} }; }, ... }When I introduced the
App
component before, I defined a contextType
for the router. That makes the router
object accessible inside the App
component using this.context.router
and, thanks to it, we can check if there has been route changes easily . We will check if there has been any URL change everytime that the App
component updates:
// Inside App component.... isURLChanged: function(){ return this.context.router.getCurrentPath() !== this.state.currentPath; }, componentWillMount: function(){ if( this.isURLChanged() ){ this.fetchDependencies(); } }, componentWillReceiveProps: function(){ if( this.isURLChanged() ){ this.fetchDependencies(); } }Now it is clear when we are going to fetch the dependencies, but we need to know what data to fetch and, to do so, we will need what is the component that
RouteHandler
will load.
The router
object has the getCurrentRoutes
method that return an Array with all the routes that matches the current URL. So if we visit /posts
, this.context.router.getCurrentRoutes()
will return something like [{route:'app'},{route:'posts'}]
. Knowing this we can get what component, AKA route handler, will be loaded:
// Inside App component getRouteHandler: function(){ var currentRoutes = this.context.router.getCurrentRoutes(), i = 0, handler = currentRoutes[0] ; // Find this component as route handler while( currentRoutes[ i ].handler !== this.constructor ) i++; // Return the next handler, our child return currentRoutes[ i + 1 ].handler; },
Once we have the component that is going to be mounted, getting its dependencies is as easy as:
// Inside App component fetchDependencies: function(){ var deps = this.getRouteHandler().deps(); .... }
That's all, we already know what data dependencies we need to fetch from the server. In this part the only tool we have introduced is react-router, them most popular router implementation for react that handles the component load and manage browser history for us.
Fetching and injecting the data
We already have access to what data the component needs, but we still need to fetch it before the component is mounted. That's not possible with the current App
's render method, data fetching takes time and the component is mounted as soon as the URL changes, so we need to delay the mounting to wait for the data. Let's modify our render method to do so:
// Inside App component
render: function() {
var handler = <h1>Loading...</h1>;
if( !this.state.loadingDeps )
handler = React.createElement(RouteHandler, this.state.handlerDeps );
return (
<div>
<Header />
<div className="content wrapper">
{ handler }
</div>
</div>
);
}</pre>
In the update we have created a cool Loading...
message for whenever the data is being loaded instead of the RouteHandler
. Whenever the data has been loaded we need to update this.state.loadingDeps
and the RouteHandler
can be mounted with its needed data already there.
We are using React.createElement
method ( see how it works ) instead of JSX to render the RouteHandler
. This way we can pass all the props to it at once, instead of writing all the JSX tag attributes.
Everything is set up just to fetch the data, so let's have a look at the fetchDependencies
method that actually do that:
// Inside App component
fetchDependencies: function(){
// We are going to refresh the dependencies
this.setState({ currentPath: currentPath, handlerDeps: {} });
var handler = this.getRouteHandler();
// If there is nothing to fetch return
if( !handler || !handler.deps )
return;
// We are going to fetch data
this.setState( { loadingDeps: true } );
var me = this,
router = this.context.router,
handlerDeps = handler.deps( router.getCurrentParams(), router.getCurrentQuery() )
;
this.fetch( handlerDeps )
.then( function( deps ){
// Update the deps to load the route handler
me.setState({
loadingDeps: false,
handlerDeps: deps
});
})
.catch( function( err ){
console.log( err.stack || err );
})
;
}
First thing to notice is how the state of the App
component is updated before start fetching, in order to display the loading message automatically while the user waits. As you can see, as soon as the data has been fetched, we set it in state
's handlerDeps
and that will refresh the App
, rendering the component with the data.
Second, we are passing the current URL parameters and query to the static deps
method:
handlerDeps = handler.deps( router.getCurrentParams(), router.getCurrentQuery() )
That will let a component different show data depending on the current URL. In our app we have a route /post/:id
handled by the Post
component. That route has the id parameter, and Post
should display the details of the post that has that id, so we pass all the current parameters and even the URL query to the deps
method in order to let the Post
component fetch the right data.
Let's have a look at how the Post
component uses the id URL parameter:
var Post = React.createClass({
statics: {
deps: function( params, query ){
return { post: '/posts/' + params.id };
}
},
render: function() {
var post = this.props.post;
return (
<div className="post">
<h2>{ post.title }</h2>
<Json value={ post } />
</div>
);
}
});
As you can see, it defines the REST endpoint to hit using the id
parameter.
The last interesting thing of fetchDependencies
methods is that it is using promises to know when the data has been fetched. Devs don't like promises so much lately because they hide errors and they are not cancellable. In this case they are really handy, because promises let's us fetch data from an unknown number of endpoints at the same time. We really don't care how many requests we are going to do to get all the data, we can use methods like all
from the Q promise library to get the response of all of them when they are finished.
For example, the User component displays user's details and also the list of posts of this user. It needs to fetch data from two different REST endpoints:
var User = React.createClass({
statics: {
deps: function( params ){
return {
user: '/users/' + params.id,
posts: '/posts?userId=' + params.id
};
}
},
render: function() {
var user = this.props.user;
return (
<div className="user">
<h2>{ user.name }</h2>
<Json value={ user } />
<h2 className="userPosts">{ user.name }'s posts</h2>
<Table rows={ this.props.posts } />
</div>
);
}
});
Both sets of data are fetched at the same time thanks to promises and Q.
In this part, the app uses useful libraries like:
- react-json is a cool component for editing JSON and creating forms that is still under development, but it has a lot of potential.
- Q give us a lot of tools for handling promises.
- qajax is a library that make ajax requests returning promises.
What's next?
We already have the app running, was it hard?
if( !this.state.loadingDeps )
handler = React.createElement(RouteHandler, this.state.handlerDeps );
return (
<div>
<Header />
<div className="content wrapper">
{ handler }
</div>
</div>
);
}</pre>
In the update we have created a cool Loading...
message for whenever the data is being loaded instead of the RouteHandler
. Whenever the data has been loaded we need to update this.state.loadingDeps
and the RouteHandler
can be mounted with its needed data already there.
We are using React.createElement
method ( see how it works ) instead of JSX to render the RouteHandler
. This way we can pass all the props to it at once, instead of writing all the JSX tag attributes.
Everything is set up just to fetch the data, so let's have a look at the fetchDependencies
method that actually do that:
// Inside App component fetchDependencies: function(){ // We are going to refresh the dependencies this.setState({ currentPath: currentPath, handlerDeps: {} }); var handler = this.getRouteHandler(); // If there is nothing to fetch return if( !handler || !handler.deps ) return; // We are going to fetch data this.setState( { loadingDeps: true } ); var me = this, router = this.context.router, handlerDeps = handler.deps( router.getCurrentParams(), router.getCurrentQuery() ) ; this.fetch( handlerDeps ) .then( function( deps ){ // Update the deps to load the route handler me.setState({ loadingDeps: false, handlerDeps: deps }); }) .catch( function( err ){ console.log( err.stack || err ); }) ; }
First thing to notice is how the state of the App
component is updated before start fetching, in order to display the loading message automatically while the user waits. As you can see, as soon as the data has been fetched, we set it in state
's handlerDeps
and that will refresh the App
, rendering the component with the data.
Second, we are passing the current URL parameters and query to the static deps
method:
handlerDeps = handler.deps( router.getCurrentParams(), router.getCurrentQuery() )
That will let a component different show data depending on the current URL. In our app we have a route /post/:id
handled by the Post
component. That route has the id parameter, and Post
should display the details of the post that has that id, so we pass all the current parameters and even the URL query to the deps
method in order to let the Post
component fetch the right data.
Let's have a look at how the Post
component uses the id URL parameter:
var Post = React.createClass({ statics: { deps: function( params, query ){ return { post: '/posts/' + params.id }; } }, render: function() { var post = this.props.post; return ( <div className="post"> <h2>{ post.title }</h2> <Json value={ post } /> </div> ); } });
As you can see, it defines the REST endpoint to hit using the id
parameter.
The last interesting thing of fetchDependencies
methods is that it is using promises to know when the data has been fetched. Devs don't like promises so much lately because they hide errors and they are not cancellable. In this case they are really handy, because promises let's us fetch data from an unknown number of endpoints at the same time. We really don't care how many requests we are going to do to get all the data, we can use methods like all
from the Q promise library to get the response of all of them when they are finished.
For example, the User component displays user's details and also the list of posts of this user. It needs to fetch data from two different REST endpoints:
var User = React.createClass({ statics: { deps: function( params ){ return { user: '/users/' + params.id, posts: '/posts?userId=' + params.id }; } }, render: function() { var user = this.props.user; return ( <div className="user"> <h2>{ user.name }</h2> <Json value={ user } /> <h2 className="userPosts">{ user.name }'s posts</h2> <Table rows={ this.props.posts } /> </div> ); } });
Both sets of data are fetched at the same time thanks to promises and Q.
In this part, the app uses useful libraries like:
This article doesn't intend to be really strict about how defining data dependencies must be done, it just shows that it is achievable and it will boost our productivity when creating components.
In Facebook's conf they show that their Relay framework can fetch data for components deep in the component hierarchy, all at once, a really cool feature that is not explored in this article.
Also, the approach explained in this article would make two components that request the same data at the same time receive two different copies of the data. If one updates the data, the other component would get out of sync. In frontends, it is important that when one entity is updated, all the components that depends on that entity get notified. I will tackle that matter in my next post, so stay tuned ;)