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