reactjsreact-routerrelayjsreact-router-relay

How to make Relay and React Routing work properly?


I am starting out with Relay and trying to make my routing work properly. Unfortunately, I am not having much of luck.

Here is the error I am getting:

Uncaught TypeError: Component.getFragment is not a function

Here is the code I have for your reference:

index.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import Relay from 'react-relay';
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import {RelayRouter} from 'react-router-relay';

import App from './modules/app';
import Home from './modules/home';

const AppQueries = {
  store: (Component) => Relay.QL `query {
    store {
      ${Component.getFragment('store')}
    }
  }`
};

ReactDOM.render(
  <RelayRouter history={browserHistory}>
    <Route path='/' component={App} queries={AppQueries}>
      <IndexRoute component={Home}/>
    </Route>
  </RelayRouter>, 
document.getElementById('ch-root'));

app.jsx

import React, {Component} from 'react';
import Relay from 'react-relay';
import Header from './ui/header';
import Footer from './ui/footer';

class App extends Component {
  render() {
    return (
      <div id="ch-container">
        <Header/>
        <section id="ch-body">
          {this.props.children}
        </section>
        <Footer/>
      </div>
    );
  }
}

App = Relay.createContainer(App, {
  fragments: {
    store: (Component) => Relay.QL `
      fragment on Store {
        ${Component.getFragment('store')}
      }
     `
  }
});

export default App;

home.jsx

import React, {Component} from 'react';
import Relay from 'react-relay';
import Loader from './ui/loader';
import AccountSelector from './account/account-selector';

const APP_HOST = window.CH_APP_HOST;
const CURR_HOST = `${window.location.protocol}//${window.location.host}`;

class Home extends Component {
  state = {
    activeAccount: null,
    loading: true
  }

  render() {
    const {activeAccount, loading} = this.state;

    if (loading) {
      return <Loader/>;
    }

    if (!activeAccount && !loading) {
      return <AccountSelector/>;
    }

    return (
      <h1>Hello!</h1>
    );
  }
}

Home = Relay.createContainer(Home, {
  fragments: {
    store: () => Relay.QL `
      fragment on Store {
        accounts {
          unique_id,
          subdomain
        }
      }
     `
  }
});

export default Home;

UPDATE

I made few changes suggested by freiksenet as below. But that raises the following two issues:

  1. What would happen when I change the route and a component other than Home gets rendered by the App component?
  2. I now get this error:

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

Here are the changes:

index.jsx

const AppQueries = {
  store: () => Relay.QL `query {
    store 
  }`
};

app.jsx

import Home from "./home";

...

App = Relay.createContainer(App, {
  fragments: {
    store: () => Relay.QL `
      fragment on Store {
        ${Home.getFragment('store')}
      }
     `
  }
});

Solution

  • Fragment definitions don't actually get Components as arguments, but a map of variables of the container, you only need to use them to have conditional fragments based on variable values.

    Relay Route queries don't accept any arguments.

    Here are the changes you need to make.

    Route queries in Relay just need to specify the root query you are going to retrieve in this route, without the fragment.

    index.jsx

    const AppQueries = {
      store: () => Relay.QL `query {
        store 
      }`
    };
    

    Your App component actually doesn't use any relay data, so you can just export normal component instead of container.

    export default class App extends Component {
    

    If you'd need to pass some Relay data to it, you don't need to include child fragments, because fragments inclusion is only needed when you directly render other containers as direct children (not as this.props.children).

    Then in Router you need to move the queries to Home.

    ReactDOM.render(
      <RelayRouter history={browserHistory}>
        <Route path='/' component={App}>
          <IndexRoute component={Home} queries={AppQueries} />
        </Route>
      </RelayRouter>, 
    document.getElementById('ch-root'));