2
votes

I'm learn Relay, and I got it working, but sometimes it feels like it was by chance :)

I'm sorry if I am making beginners mistakes here, I've read the manual and all the examples, but I just can't seem to wrap my head around it all.

Anyway, I have the following problem right now.

My schema looks like this:

{
    viewer(token:$anAuthToken) {
        actor {
          id,
          email,
          ...
        }
    }
}

All data is accessed under { viewer }, which takes care of authentication. The actor field is just one of many.

Actor is an instance of UserType, the currently logged in user.

I want a sane component hierarchy, where the top component only gets a user as props (and provides a fragment for the properties it needs). But I'm not doing it right, according to Relay, and I'm not sure what I'm doing wrong.

My code looks like this (quite chaotic at the moment, due to me testing things out):

class UserRegistrationPage extends React.Component {

    render() {
        const user = this.props.user;
        return (
            <View>
                <Text>Email: {user.email}</Text>
            </View>
        );
    }

    submit(model) {
        const user = this.props.user;

        this.props.relay.commitUpdate(
            new UpdateUserMutation({
                id: user.id,
                ...model
            }, {
                onSuccess: response => {
                    console.log("SUCCESS!");
                    console.log(response);
                },
                onFailure: () => {
                    console.log("FAIL!");
                }
            }));
    }

}

UserRegistrationPage = Relay.createContainer(UserRegistrationPage, {
    fragments: {
        user: () => Relay.QL`
            fragment on User {
                email
            }
        `,
    },
});

//////////////////////////////

class UserRegistrationViewer extends React.Component {

    render() {
        return <UserRegistrationPage user={this.props.actor} />
    }

}

UserRegistrationViewer = Relay.createContainer(UserRegistrationViewer, {
    fragments: {
        actor: () => Relay.QL`
            fragment on Viewer {
                actor {
                    ${UserRegistrationPage.getFragment('user')},
                }
            }
        `,
    },
});

/////////////////////////////////

class QueryConfig extends Relay.Route {
    static routeName = 'UserRegistrationRoute';
    static prepareParams = routeConfigParamsBuilder;
    static queries = {
        actor: (Component) =>
            Relay.QL`
                     query {
                        viewer(token:$token) {
                            ${Component.getFragment('actor')},
                        }
                     }
        `,
    };
}

export const UserRegistrationPageComponent = (parentProps) => {
    return (
        <Relay.Renderer
            environment={Relay.Store}
            Container={UserRegistrationViewer}
            queryConfig={new QueryConfig()}
            render={({done, error, props, retry, stale}) => {
        if (error) {
          return <View><Text>Error!</Text></View>;
        } else if (props) {
          return <UserRegistrationViewer {...parentProps} {...props} />;
        } else {
          return <View><Text>Loading...</Text></View>;
        }
      }}
        />
    )
};

And I'm getting the following error.

Warning: RelayContainer: component UserRegistrationPage was rendered with variables that differ from the variables used to fetch fragment user. The fragment was fetched with variables (not fetched), but rendered with variables {}. This can indicate one of two possibilities: - The parent set the correct variables in the query - UserRegistrationPage.getFragment('user', {...}) - but did not pass the same variables when rendering the component. Be sure to tell the component what variables to use by passing them as props: <UserRegistrationPage ... />. - You are intentionally passing fake data to this component, in which case ignore this warning.

So I have a couple of questions.

1) Why do I get that error, and how do I fix it? I don't understand why it would complain about fragment variables when none of the fragments have any variables.

2) How can I make it so that UserRegistrationPage gets this.props.user? Should I make a component tree hierarchy that maps the query response? At some point I had this.props.actor.user, but I don't want the component to know about the query response structure, only the object that it is interested in.

3) Is the name of the query coupled with the name of the fragments in some way?

Any help, suggestions for improvements, tips and answers are very welcome, I'm starting to bang my head against the wall here :)

1

1 Answers

6
votes

I finally understood a few details that eluded me before. I'll post them here, in case it helps anyone else that is struggling with Relay.

class QueryConfig extends Relay.Route {
    static routeName = 'UserRegistrationRoute';
    static prepareParams = routeConfigParamsBuilder;
    static queries = {
        actor: (Component) =>
            Relay.QL`
                     query {
                        viewer(token:$token) {
                            ${Component.getFragment('actor')},
                        }
                     }
        `,
    };
}

The query here is misleading when named "actor", because it doesn't return the actor. It returns the root of the query which is the viewer. Also, the fragment that is included is for viewer, not for actor.

Let's update it.

class QueryConfig extends Relay.Route {
    static routeName = 'UserRegistrationRoute';
    static prepareParams = routeConfigParamsBuilder;
    static queries = {
        viewer: (Component) =>
            Relay.QL`
                     query {
                        viewer(token:$token) {
                            ${Component.getFragment('viewer')},
                        }
                     }
        `,
    };
}

Done. Let's see what's wrong with the first container.

UserRegistrationViewer = Relay.createContainer(UserRegistrationViewer, {
    fragments: {
        actor: () => Relay.QL`
            fragment on Viewer {
                actor {
                    ${UserRegistrationPage.getFragment('user')},
                }
            }
        `,
    },
});

Same thing here, names are misleading. This container doesn't feed an actor into the component, it feeds the viewer.

Let's update it.

UserRegistrationViewer = Relay.createContainer(UserRegistrationViewer, {
    fragments: {
        viewer: () => Relay.QL`
            fragment on Viewer {
                actor {
                    ${UserRegistrationPage.getFragment('user')},
                }
            }
        `,
    },
});

So, only the name of the fragment changed here.

Now, the UserRegistrationViewer component gets a prop "viewer" (as specified by fragments) that is of type ViewerType. And we know that the viewer contains "actor" which is an instance of UserType. UserRegistrationPage requires a prop "user" that is of UserType, as specified by its fragments.

So lets update the component.

class UserRegistrationViewer extends React.Component {

    render() {
        return <UserRegistrationPage user={this.props.viewer.actor} />
    }

}

Now we have a UI component that doesn't know about the query response hierarchy, and everything is working!

I'm still not exactly sure why I got that error though :)

Hope this can help someone!