Skip to content

Instantly share code, notes, and snippets.

@jameslaneconkling
Last active August 8, 2017 20:23
Show Gist options
  • Save jameslaneconkling/c74b32c7c8922285d4cadb4a54950115 to your computer and use it in GitHub Desktop.
Save jameslaneconkling/c74b32c7c8922285d4cadb4a54950115 to your computer and use it in GitHub Desktop.
Simple React-Falcor bindings to connect your components to your falcor graph.

React Falcor Bindings

A simple rxjs-powered Higher Order Component to bind React views to the Falcor graph.

The intent here is to allow for GraphQL/Relay-esque declarative data fetching at the component level, without requiring an entire framework for support. This means the HOC bindings should play well along side other data model frameworks like Redux.

The HOC works by running queries against a falcor model, and merging the result stream into a component's props, specifically by injecting the props graphFragment (containing the query result) and graphFragmentStatus (containing the string next, complete or error).

const TodosList = ({ from, to, graphFragment, graphFragmentStatus }) => {
  if (graphFragmentStatus === 'error') {
    return <h2>Error loading search</h2>;
  }
  
  return (
    <div>
      <h1>My Todos {graphfragmentStatus === 'next' && <LoadingSpinner />}</h1>
      <div>Showing {from} to {to} of {graphFragment.todos.length} todos</div>
      <ul>
        {graphFragment.todos.map(result => (
           <li>
             <h3>{result.label}</h3>
             <p>{result.description}</p>
           </li>
        ))}
      </ul>
    </div>
  );
};

const TodosListContainer = compose(
  connectFalcor(({ from, to }) => (
    Observable.from(falcor.get(
      ['todos', { from, to }, ['label', 'description']],
      ['todos', 'length']
    ).progressively())
  ))
)(TodosList);

Inspired in part by graphistry/falcor-react-schema.

License

ISC

import {
compose,
hoistStatics,
mapPropsStream,
setObservableConfig,
rxjsObservableConfig,
withProps
} from 'recompose';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/merge';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/scan';
import 'rxjs/add/operator/auditTime';
import { animationFrame } from 'rxjs/scheduler/animationFrame';
/*
* FalcorConnect HOC
*/
const connectFalcor = (
propsToGraphQuery,
{
debounce = false,
errorHandler = error => Observable.of({ error, graphFragmentStatus: 'error' })
} = {}
) => BaseComponent =>
hoistStatics(mapPropsStream((props$) => {
const graphQueries$ = (debounce ? props$.debounceTime(debounce) : props$)
.merge(change$.withLatestFrom(props$, (_, props) => props))
.switchMap((props) => {
const graphQuery$ = propsToGraphQuery(props);
return graphQuery$
.map(graphFragment => ({ graphFragment, graphFragmentStatus: 'next' }))
.merge(graphQuery$.last().map(graphFragment => ({ graphFragment, graphFragmentStatus: 'complete' })))
.catch(err => errorHandler(err, props));
});
return props$
.map(props => ({ ...props, graphFragmentStatus: 'next' }))
.merge(graphQueries$.withLatestFrom(props$, (graphQuery, props) => ({ ...props, ...graphQuery })))
.scan((prev, next) => ({
...next,
graphFragment: next.graphFragment || prev.graphFragment || {}
}), {})
.auditTime(0, animationFrame);
}))(BaseComponent);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment