Skip to content

Instantly share code, notes, and snippets.

@jasonphillips
Last active December 14, 2015 03:15
Show Gist options
  • Save jasonphillips/1de71dd6c494a3b0a34e to your computer and use it in GitHub Desktop.
Save jasonphillips/1de71dd6c494a3b0a34e to your computer and use it in GitHub Desktop.
Declarative, Isomorphic Falcor data in React.js

Declarative, Isomorphic Falcor data in React.js

This example lays out the basic structure within which I have been using Falcor in a manner comparable to a GraphQL / Relay implementation, with data fetching needs specified declaratively in decorators on each component, and with those paths composable by passing a root path to child components to which they may append further paths.

React-resolver provides the basic setup for the decorators and makes it possible to render the components code smoothly on the server with all data fetched as it would be on the client; it then passes resolved data into the decorated component as a property.

This general structure has many of the advantages of Relay (declarative data needs per component; composable queries; fast, bundled fetching with a graph-based structure; server-side rendering) but with a simpler setup, given the relative ease and flexibility of composing a Falcor endpoint and writing nested path queries.

// Main Post component, which initiates data fetching
import React from "react";
import { resolve, context } from "react-resolver";
import PostBody from "./PostBody.js";
import PostMeta from "./PostMeta.js";
/**
* Falcor routes constructed below:
*
* postsById[id]...
* .['title','id']
* .(sub paths requested by PostMeta component)
* .(sub paths requested by PostBody component)
**/
@context("model")
@resolve("post", ({ model, params }) =>
model.get.apply(model, [
["postsById", params.postid, ["title", "id"]],
].concat(PostMeta.falcorPaths.post(["postsById", params.postid]))
.concat(PostBody.falcorPaths.post(["postsById", params.postid]))
).then(
(data) => data.json.postsById[params.postid]
)
)
export default class Post extends React.Component {
static displayName = "Post"
render() {
return (
<div className="Post">
<h3>{this.props.post.title}</h3>
<PostBody post={this.props.post} teaserOnly={true}/>
<PostMeta post={this.props.post}/>
</div>
);
}
}
import React from "react";
export default class PostBody extends React.Component {
static displayName = "PostBody"
static falcorPaths = {
post: function(route) {
return [
route.concat(["content"]),
route.concat(["teaser"])
];
},
}
render() {
let htmlContent = this.props.teaserOnly ? this.props.post.teaser : this.props.post.content;
return (
<section>
<div dangerouslySetInnerHTML={{__html: htmlContent}}/>
</section>
);
}
}
// Post Metadata component, which adds paths to parent's query
import React from "react";
import _ from "lodash";
export default class PostMeta extends React.Component {
static displayName = "PostMeta"
/**
* falcorPaths method will be called by parent
* with single parameter of route path to object,
* to which to append any deeper paths needed
**/
static falcorPaths = {
post: function(route) {
return [
route.concat(["author", "name"]),
route.concat(["date"]),
route.concat(["terms", "category", { from: 0, to: 5 }, ["name", "slug", "id"]]),
];
},
}
render() {
return (
<div className="postMeta">
<div>
{_.values(this.props.post.terms.category).map((category, i) =>
<div key={i} className="chip">{category.name}</div>
)}
</div>
<i>Updated {this.props.post.date} by {this.props.post.author.name}</i>
</div>
);
}
}
import React from "react";
/* A function creating your local Falcor model at top of app,
* to be passed down tree through context
*
* Seethe Post.js component, where the model is used in data fetching
* via the @context decorator
**/
import makeModel from "../model/falcor.js";
export default class App extends React.Component {
static displayName = "App"
static childContextTypes = {
model: React.PropTypes.object,
}
constructor(props) {
super(props);
this.model = makeModel();
}
getChildContext() {
return {
model: this.model,
};
}
render() {
return (
<div>{this.props.children}</div>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment