Relay: [Modern] Relay with hot reloading

Created on 29 May 2017  路  6Comments  路  Source: facebook/relay

I'm trying to use react-hot-loader with my Relay modern app. I'm facing an issue with it. The way hot reloading works, I'm rendering AppContainer as root DOM node which renders my Relay QueryRenderer as it's child.

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById("root")
  );
};

render(RootQueryRenderer);

if (module.hot) {
  module.hot.accept("./components/root", () => render(RootContainer));
}
export default () => (
  <QueryRenderer
    environment={relayEnvironment}
    query={graphql`
      query rootQuery {
        user {
           id
        }
      }
    `}
    variables={{}}
    render={({ error, props, ...rest }) => {
      if (props) {
        return <Root {...props} />;
      } else {
        return <div>Loading</div>;
      }
    }}
  />
);

On every change on hot loading, QueryRenderer re-renders all the components. First it need to return empty loading div and then when the network request if fulfilled Root app gets returned with new props. Return loading div at first re-renders the entire app.

Most helpful comment

Thanks @josephsavona for advice. I found there was two reasons what caused this in my app and was able to get it working.

First, I replaced the functional component to a ReactClass component which renders QueryRenderer and keeps query and environment as state and passes down to query renderer as mentioned in code below

export default class RootQuery extends React.Component {
  state = {
    query: graphql`
      query rootQuery {
        user {
          ...Account_user
        }
      }
    `,
    environment: relayEnvironment,
  };

  render() {
    return (
      <QueryRenderer
        environment={this.state.environment}
        query={this.state.query}
        variables={{}}
        render={({ error, props }) => {
          if (props) {
            return <Root {...props} />;
          } else {
            return <div />;
          }
        }}
      />
    );
  }
}

This way I was able to ensure QueryRenderer doesn't gets new query or environment when hot reloading happens and it's not doing a refetch. Please let me know if this is a correct way to approach this.

Second, I'm using react-router-v4's withRouter HOC to make my container aware of routes which was remounting my container. I'm still looking into how to make withRouter not remount my container.

All 6 comments

Thanks for posting. Can you clarify: how would you expect/prefer this to work?

Note that QueryRenderer (and all the containers) will forcibly re-render whenever the environment instance, root query, or root variables change. This is to ensure that they are rendering the correct data. If the environment changes, that means different cached data. If the query/variables change, different data is being selected, etc.

I expect during development whenever any changes happen in my app code, for example css of a component is changed, only that component is re-rendered to reflect css changes. This should not re-render entire app. I'm losing entire state of application because I need to return a loading div to QueryRenderer.

In general we didn't really consider hot reloading integration during the development of Relay Modern so this is likely to be a bit rough. As a first step i would look at what is causing QuerRenderer to refetch - is it getting unmounted and remounted? Is it getting new props that cause it to refetch?

Thanks @josephsavona for advice. I found there was two reasons what caused this in my app and was able to get it working.

First, I replaced the functional component to a ReactClass component which renders QueryRenderer and keeps query and environment as state and passes down to query renderer as mentioned in code below

export default class RootQuery extends React.Component {
  state = {
    query: graphql`
      query rootQuery {
        user {
          ...Account_user
        }
      }
    `,
    environment: relayEnvironment,
  };

  render() {
    return (
      <QueryRenderer
        environment={this.state.environment}
        query={this.state.query}
        variables={{}}
        render={({ error, props }) => {
          if (props) {
            return <Root {...props} />;
          } else {
            return <div />;
          }
        }}
      />
    );
  }
}

This way I was able to ensure QueryRenderer doesn't gets new query or environment when hot reloading happens and it's not doing a refetch. Please let me know if this is a correct way to approach this.

Second, I'm using react-router-v4's withRouter HOC to make my container aware of routes which was remounting my container. I'm still looking into how to make withRouter not remount my container.

Cool, glad you got the Relay portion working. Using a stateful component seems like a reasonable approach.

Moving environment and query into state worked (Relay no longer refetches on a hot reload).

<QueryRenderer
  environment={this.state.environment}
  query={this.state.query}
  ...
/>

Thanks @thenaineshgohil!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bondanherumurti picture bondanherumurti  路  3Comments

mike-marcacci picture mike-marcacci  路  3Comments

jstejada picture jstejada  路  3Comments

johntran picture johntran  路  3Comments

janicduplessis picture janicduplessis  路  3Comments