Define the data to fetch in a declarative way with React

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:

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.
We are going to add state to our 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 (
		&lt;div&gt;
			&lt;Header /&gt;
			&lt;div className="content wrapper"&gt;
			{ handler }
			&lt;/div&gt;
		&lt;/div&gt;
	);
}</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 }&#39;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?

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 ;)

© arqex 2023
Someday,
we'll find ourselves
far, far away...
Transitions here have been created using a library of mine:
React interactable.
This awesome hyperspace effect thanks to this codepen from Noah Blon.
Software development can be fun!
Javier Márquez
Software developer