Relay
Hardest parts of React:
- Data fetching
- Where
- How
- When
- Managed/unmanaged state
Facebook said:
Use Flux, that's what we do!
What's Flux?
Flux is not a framework
So people made their own...
- Fluxxor
- Reflux
- Fluxible
- Alt
- McFly
- Lux
- Marty
How does it help me?
One-way data flow
Meaningful unit tests
Easier debugging
Single-source of truth: Stores
componentWillMount()
class MovieInfo extends React.Component {
componentWillMount() {
fetchMovieInfo(this.props.movieId, this.setState);
}
render() {
if (!this.state || !this.state.movie) {
return null;
}
return (
<div className="movie-info">
<h2>{this.state.movie.name}</h2>
<RatingBar rating={this.state.movie.rating} />
<p className="movie-summary">
{this.state.movie.summary}
</p>
</div>
);
}
}
Flux
class MovieInfo extends React.Component {
construct(props) {
super(props);
this.state = this.getStateFromStores();
}
componentDidMount() {
MovieStore.addChangeListener(this.onChange);
}
componentWillUnmount() {
MovieStore.removeChangeListener(this.onChange);
}
getStateFromStores() {
return { movie: MovieStore.getMovie(this.props.movieId) };
}
onChange() {
this.setState(this.getStateFromStores());
}
render() {
return (/* ... */);
}
}
But it still feels... Incomplete?
Relay deals with
data fetching...
Cleverly.
Let me give you an example.
Part 1; the pure view component
class Timeline extends React.Component {
render() {
var match = this.props.match;
return (
<div className="timeline">
<span className="team-name">{match.homeTeam.name}</span>
<span className="goalCount">{match.homeTeam.goals}</span>
-
<span className="team-name">{match.awayTeam.name}</span>
<span className="goalCount">{match.awayTeam.goals}</span>
<ProgressBar elapsedTime={match.elapsedTime} />
</div>
);
}
}
Part 2; the container component
let TimelineContainer = Relay.createContainer(Timeline, {
queries: {
match: graphql`
Match {
homeTeam {
edges { goals, node { Participant { name } } }
},
awayTeam {
edges { goals, node { Participant { name } } }
},
status,
elapsedTime
}
`;
}
});
export default TimelineContainer;
Repeat: Part 1; Pure view comp.
class IncidentList extends React.Component {
render() {
var incidents = this.props.match.incidents;
return (
<ul className="incident-list">
{incidents.map(
incident => <Incident incident={incident} />
)}
</ul>
);
}
}
Repeat: Part 2; Container.
let IncidentListContainer = Relay.createContainer(IncidentList, {
queries: {
match: graphql`
Match {
incidents {
edges {
node {
${Incident.getQuery('incident')}
}
}
}
}
`;
}
});
export default IncidentListContainer;
Annnd once more:
class Incident extends React.Component {
render() {
var incident = this.props.incident;
return (<div className="incident">{/* ... */}</div>);
}
}
export default Relay.createContainer(Incident, {
queries: {
incident: graphql`
Incident {
elapsed,
type,
text
}
`;
}
});
- Routes
- Traverses tree
- Collects queries
- Queries for data
- Stores data in store
- Renders component tree
No over-fetching
(and no under-fetching)
A single store
(to rule them all)
Data queries are smart
(do I have this in cache already?)
Data queries are very smart
Data query in progress?
Only fetch unrequested data.
Tracks dependencies
Which is friggin' awesome (implicit subscriptions).
Reveals only explicitly defined data
(no "leaks" or "assumptions")
Optimistic updates
With error rollback
Deferred data
with a clever API
(available? missing? fetching?)
shouldComponentUpdate
...out of the box...
Excited yet?
Downsides?
- Is it too "magic"?
- Debuggable? (though.. React)
- Single store... workable?
What can we learn?
- Container components is a Good Idea™
- Similar frameworks on the way?
- "Facebook just taught us all how to build websites"
Relay
By Espen Hovlandsdal
Relay
- 1,322