Getting Started With Gambit

Gambit is a library for use with Redux and React that makes building API driven web-apps very easy.

The idea behind Gambit is that Redux is a great tool (thanks Dan Abramov) but it is necessarily low-level. This can make a lot of the common tasks of building API driven apps a bit of an ordeal.

Gambit provides an interface for making those common tasks much easier and more pleasant. A quick example is creating a contained component that has to speak to an API. Taking a simple stateless component that displays a list of tweets:

const TweetList = ({ tweets, currentUser }) => {
  return (
    <div>
      {tweets.map(tweet => ({
        <p>{tweet.get('content')}</p>
        <div>
          <span>Posted By:</span>
          <span>
            {tweet.get('user') === currentUser.get('name') ? 'You' : tweet.get('user')}
           </span>
      })}
     </div>
  );
 };

With Gambit, connecting this to your store and api is simple:

const TweetListContainer = createContainer(TweetList, {
  fetch: {
    tweets: {
      as: state => state.twitter.get('tweets'),
      grab: dispatch => currentValue => {
        if (currentValue.count() === 0) return dispatch(getMyTweets());
      },
    },
    currentUser: {
      as: state => state.user.get('currentUser'),
      grab: dispatch => currentValue => {
        if (!currentValue) return dispatch(getCurrentUser());
      },
    },
  },
  pending() {
    return <h2>Loading</h2>;
  },
});

Whereas in plain Redux, your container would need to be something more complicated and harder to follow (adapted from Redux: Read me):

class TweetListContainer extends Component {

  doFetching(props = false) {
    const { tweets, currentUser } = props || this.props;
    if (!tweets.count()) dispatch(getMyTweets());
    if (!currentUser) dispatch(getCurrentUser());
  }

  componentDidMount() {
    this.doFetching();
  }

  componentWillReceiveProps(nextProps) {
    this.doFetching(nextProps);
  }

  render() {
    const {
      tweets,
      isFetchingUser,
      isFetchingTweets,
      currentUser,
    } = this.props;

    return (
      {isFetchingUser || isFetchingTweets (
        <h2>Loading...</h2>
      )}
      {!isFetchingUser && !isFetchingTweets (
        <TweetList tweets={tweets} currentUser={currentUser} />
      )}
    );
  }
}

TweetListContainer.propTypes = {
  tweets: PropTypes.array.isRequired,
  currentUser: PropTypes.object.isRequired,
  isFetchingTweets: PropTypes.bool.isRequired,
  isFetchingUser: PropTypes.bool.isRequired,
  dispatch: PropTypes.func.isRequired
}

function mapStateToProps(state) {
  return {
    tweets: state.twitter.get('tweets'),
    isFetchingTweets: state.twitter.get('isFetchingTweets'),
    isFetchingUser: state.twitter.get('isFetchingUser'),
    currentUser: state.user.get('currentUser'),
  };
}

export default connect(mapStateToProps)(TweetListContainer);

Gambit also decreases the boilerplate required for other parts of this down the chain:

  • Creating ActionCreators that fire actions when a call has started, is finished and has failed.
  • Ensuring ActionCreators only cause API calls when desired, not just when a component is loaded.
  • Creating Reducers to update state