Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wire data to a deep component in react-router-relay?

I have a route like this

<Route path="/search" component={Search}>

The basic Search component looks likes this

class Search extends React.Component {
  constructor (props) {
    super(props)
    this.state = {query: ''}
  }
  handleSubmit (event) {
    event.preventDefault()
    this.setState({query: this.refs.queryInput.value})
  }
  renderSearchResult() {
    if (this.state.query === '')
      return <EmptySearchResult />
    else
      return <SearchResult query={this.state.query}/>
  }
  render() {
    return (
      <div className="searchContainer">
        <div className="row">
          <div className="search">
            <form onSubmit={event => this.handleSubmit(event)}>
              <input className="searchInput" placeholder="robocop" ref="queryInput" />
            </form>
          </div>
        </div>
        <div className="row">
          {this.renderSearchResult()}
        </div>
      </div>
    )
  }
}

SearchResult relay container looks like this

class SearchResult extends React.Component {
  render() {
    var {viewer: {moviesByTitle: movies}} = this.props;
    return (
      <div className="searchResult">
        {movies.edges.map((edge,i) =>
          <div key={i} className="rowItem scrollRowItem">
            <Movie movie={edge.node} />
          </div>)}
      </div>
    )
  }
}

export default Relay.createContainer(SearchResult, {
  initialVariables: {
    query: '???'
  },
  fragments: {
    viewer: () => Relay.QL`
      fragment on User {
        moviesByTitle(title: $query, first: 10) {
          edges {
            node {
              ${Movie.getFragment('movie')}
            }
          }
        }
      }
    `
  }
})

Error:

Warning: RelayContainer: Expected prop `viewer` to be supplied to `SearchResult`, but got `undefined`. Pass an explicit `null` if this is intentional.

What I was trying to do inside my Search component (changes in bold)

const ViewerQueries = {
  viewer: () => Relay.QL`query { viewer }`
}

...

renderSearchResult() {
  if (this.state.query === '')
    return <EmptySearchResult />
  else
    return <SearchResult query={this.state.query} queries={ViewerQueries} />
}

But of course that doesn't work because the queries need to somehow be attached to the Route


Questions

  1. My Search Component is just a presentational component that does not need data of its own. Instead, it just feeds a query prop to the SearchResult relay container. How should I structure the Components and Relay containers to attach them to the Route properly?

  2. How can I use the SearchResult query prop to set a variable in the relay query fragment?

  3. I don't understand the "viewer" abstraction in general – most examples I've seen are very contrived and don't show how they'd work in a more realistic setting; eg users with different access to different resources, or different parts of your program that are intended to view different slices of data

like image 633
Mulan Avatar asked Mar 07 '17 15:03

Mulan


1 Answers

Using Relay's modern API, compose a refetchContainer from SearchResult component because of the need "an option to execute a new query with different variables and render the response of that query instead when the request comes back".

import React, { Component } from 'react';
import debounce from 'debounce';
import { graphql, createRefetchContainer } from 'react-relay';

class SearchResult extends React.Component {
  componentWillReceiveProps(nextProps) {
    if (this.props.query != nextProps.query) {
      debounce(() => this._searchMovies(), 600);
    }
  }

  _searchMovies() {
    const refetchVariables = fragmentVariables => ({
      query: this.props.query
  });

    this.props.relay.refetch(refetchVariables, null);
  }

  render() {
    var { viewer: { moviesByTitle: movies } } = this.props;
    return (
      <div className="searchResult">
        {movies.edges.map((edge, i) =>
          <div key={i} className="rowItem scrollRowItem">
            <Movie movie={edge.node} />
          </div>)}
      </div>
    )
  }
}


// Similar query syntax as Relay Classic
// Only syntactic difference is with absence of Movie.getFragment
module.exports = createRefetchContainer(SearchResult, {
    viewer: graphql`
      fragment SearchResult_viewer on SearchResult {
        @argumentDefinitions(
          query: {type: "String", defaultValue: ""}
        ) {
        moviesByTitle(title: $query, first: 10) {
          edges {
            node {
              ...Movie_movie
            }
          }
        }
      }`
    },
    graphql`
      query MoviesRefetchQuery($query: String) {
        viewer {
          ...SearchResult_viewer @arguments(query: $query)
        }
      }
   `);
like image 150
Oluwafemi Sule Avatar answered Nov 01 '22 16:11

Oluwafemi Sule