Link Github issue:
We have encountered an strange behaviour of Relay. I will try to explain the best I can.
So we have an "main" relay container that fetches the data for corresponding store, and also includes and fragment from Ticket container. Ticket container render out custom table that has filter and sorting. So you can see that in StoreFrom ponent StoreTicketList container is import all required props are passed like Store fragment.
The problem occurs when you try to filter StoreList Ticket, I mean set filter or sort relay variables. You will get this error:
Warning: RelayContainer: ponent TicketList
was rendered with variables that differ from the variables used to fetch fragment Store
. The fragment was fetched with variables {"first":5,"after":null,"last":null,"before":null,"sort":null,"filter":null}
, but rendered with variables {"first":5,"after":null,"last":null,"before":null,"sort":null,"filter":{"authorAccount":{"email":{"__e":"[email protected]"}}}}
. This can indicate one of two possibilities:
- The parent set the correct variables in the query - TicketList.getFragment('Store', {...})
- but did not pass the same variables when rendering the ponent. Be sure to tell the ponent what variables to use by passing them as props: <TicketList ... first={...} after={...} last={...} before={...} sort={...} filter={...} />
.
- You are intentionally passing fake data to this ponent, in which case ignore this warning.
But those filter/sort variables are on StoreTicketList and they arent passed dow from parent to child container like in this case Store container to StoreListTicket container.
export class StoreForm extends React.Component {
constructor(props) {
super(props);
const { Viewer: { store } } = props;
this.state = {
number: store && store.number !== null ? store.number : '',
};
}
handleInsert = (model) => {
console.log('Form mutation model : ', model);
};
render() {
const { Viewer, relay: { variables: { update } } } = this.props;
return (
<div>
<Form>
<FormTitle title='Store Info' />
<FormBody>
<TextField
required
fullWidth
name='number'
value={this.state.number}
floatingLabelText='Number'
/>
<StoreTicketList Store={this.props.Viewer.store} />
</FormBody>
</Form>
</div>
);
}
}
StoreForm container (main container):
export default Relay.createContainer(StoreForm, {
initialVariables: {
id: null,
update: false
},
prepareVariables({ id = null }) {
return { id, update: (id !== null) };
},
fragments: {
Viewer: (variables) => Relay.QL`
fragment on Viewer {
store(id: $id) @include(if: $update) {
id,
number
${StoreTIcketList.getFragment('Store')}
}
}
`
}
});
Ticket container:
export const StoreTicketList = Relay.createContainer(TicketList, {
initialVariables: {
first: 5,
after: null,
last: null,
before: null,
sort: null,
filter: null
},
fragments: {
Store: () => Relay.QL`
fragment on Store {
ticketConnection(first: $first, after: $after, last: $last, before: $before, sort: $sort, filter: $filter) {
count,
pageInfo {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
},
edges{
node{
created,
subject
}
}
}
}
`
}
});
We have built our own Connection Table HOC ponent that renders table for each container. In this ponent there are also sort and filter function that are using this.props.relay.setVariables()
.
So the StoreListTicket is rendering as an ConnectionTable and it passes down the relay prop object, and if user clicks on a table colum header, ponent is generating an array of sort objects.
function connectionTableHOC(ComposedComponent) {
class EnhanceTable extends React.Component {
constructor(props) {
super(props);
}
sortHandler = (sortArray) => {
const { relay, relay: { variables } } = this.props;
relay.setVariables({
first: variables.first || variables.last,
after: null,
last: null,
before: null,
sort: sortArray
});
};
filterHandler = (filterObj) => {
const { relay, relay: { variables } } = this.props;
relay.setVariables({
first: variables.first || variables.last,
after: null,
last: null,
before: null,
filter: filterObj
});
};
render() {
return <ComposedComponent {...this.props} />;
}
}
Link Github issue: https://github./facebook/relay/issues/1218
We have encountered an strange behaviour of Relay. I will try to explain the best I can.
So we have an "main" relay container that fetches the data for corresponding store, and also includes and fragment from Ticket container. Ticket container render out custom table that has filter and sorting. So you can see that in StoreFrom ponent StoreTicketList container is import all required props are passed like Store fragment.
The problem occurs when you try to filter StoreList Ticket, I mean set filter or sort relay variables. You will get this error:
Warning: RelayContainer: ponent TicketList
was rendered with variables that differ from the variables used to fetch fragment Store
. The fragment was fetched with variables {"first":5,"after":null,"last":null,"before":null,"sort":null,"filter":null}
, but rendered with variables {"first":5,"after":null,"last":null,"before":null,"sort":null,"filter":{"authorAccount":{"email":{"__e":"[email protected]"}}}}
. This can indicate one of two possibilities:
- The parent set the correct variables in the query - TicketList.getFragment('Store', {...})
- but did not pass the same variables when rendering the ponent. Be sure to tell the ponent what variables to use by passing them as props: <TicketList ... first={...} after={...} last={...} before={...} sort={...} filter={...} />
.
- You are intentionally passing fake data to this ponent, in which case ignore this warning.
But those filter/sort variables are on StoreTicketList and they arent passed dow from parent to child container like in this case Store container to StoreListTicket container.
export class StoreForm extends React.Component {
constructor(props) {
super(props);
const { Viewer: { store } } = props;
this.state = {
number: store && store.number !== null ? store.number : '',
};
}
handleInsert = (model) => {
console.log('Form mutation model : ', model);
};
render() {
const { Viewer, relay: { variables: { update } } } = this.props;
return (
<div>
<Form>
<FormTitle title='Store Info' />
<FormBody>
<TextField
required
fullWidth
name='number'
value={this.state.number}
floatingLabelText='Number'
/>
<StoreTicketList Store={this.props.Viewer.store} />
</FormBody>
</Form>
</div>
);
}
}
StoreForm container (main container):
export default Relay.createContainer(StoreForm, {
initialVariables: {
id: null,
update: false
},
prepareVariables({ id = null }) {
return { id, update: (id !== null) };
},
fragments: {
Viewer: (variables) => Relay.QL`
fragment on Viewer {
store(id: $id) @include(if: $update) {
id,
number
${StoreTIcketList.getFragment('Store')}
}
}
`
}
});
Ticket container:
export const StoreTicketList = Relay.createContainer(TicketList, {
initialVariables: {
first: 5,
after: null,
last: null,
before: null,
sort: null,
filter: null
},
fragments: {
Store: () => Relay.QL`
fragment on Store {
ticketConnection(first: $first, after: $after, last: $last, before: $before, sort: $sort, filter: $filter) {
count,
pageInfo {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
},
edges{
node{
created,
subject
}
}
}
}
`
}
});
We have built our own Connection Table HOC ponent that renders table for each container. In this ponent there are also sort and filter function that are using this.props.relay.setVariables()
.
So the StoreListTicket is rendering as an ConnectionTable and it passes down the relay prop object, and if user clicks on a table colum header, ponent is generating an array of sort objects.
function connectionTableHOC(ComposedComponent) {
class EnhanceTable extends React.Component {
constructor(props) {
super(props);
}
sortHandler = (sortArray) => {
const { relay, relay: { variables } } = this.props;
relay.setVariables({
first: variables.first || variables.last,
after: null,
last: null,
before: null,
sort: sortArray
});
};
filterHandler = (filterObj) => {
const { relay, relay: { variables } } = this.props;
relay.setVariables({
first: variables.first || variables.last,
after: null,
last: null,
before: null,
filter: filterObj
});
};
render() {
return <ComposedComponent {...this.props} />;
}
}
Share
Improve this question
edited Oct 6, 2016 at 14:22
YasserKaddour
93011 silver badges23 bronze badges
asked Jun 16, 2016 at 18:02
Mario JerkovićMario Jerković
581 silver badge7 bronze badges
2
-
It sounds like you're calling
setVariables
somewhere, but it isn't clear from the example code where that's happening. Can you also show the code that callssetVariables
? This will make it clear where the variables are changing and why the parent and child ponents disagree about their values. – Joe Savona Commented Jun 16, 2016 at 20:54 - I have updated the post with the code you have requested. – Mario Jerković Commented Jun 17, 2016 at 6:58
2 Answers
Reset to default 7It turns out you need to do two things:
First, pass the props into the ponent, as described by Joe Savona. I'm using react-relay-router, so for me that was a matter of adding this line
<Route path="interviews">
<IndexRoute ponent={InterviewsList} queries={ViewerQuery} />
<Route path=":id" ponent={InterviewSession} queries={NodeViewerQuery}
render={({ props }) => props ? <InterviewSession {...props} /> : <Loading />}/> // <--- this line
</Route>
Second, you must inject the variable's values into the getFragment
function call, like so:
fragments: {
Viewer: (variables) => Relay.QL`
fragment on Viewer {
store(id: $id) @include(if: $update) {
id,
number
${StoreTIcketList.getFragment('Store', {... variables})} // <---- this thing!
}
}
`
}
Note that if you're using getFragment
inside of your root query, variables
will be argument number two:
const NodeViewerQuery = {
node: (ponent, variables) => Relay.QL`query { // <---- extra "ponent" argument
node(id: $id) {
${ponent.getFragment('node', {...variables})}
}
}`,
(This answer crossposted from https://github./facebook/relay/issues/1218)
It looks like the parent ponent (the EnhanceTable
) is setting the variables for its child (the StoreTicketList
). This means that when the child renders it tries to render using the default variables, while the parent has fetched a different set of variables (sorted/filtered). The solution is to have EnhanceTable
tell the rendered ponent what sort/filter it is using:
function connectionTableHOC(ComposedComponent) {
class EnhanceTable extends React.Component {
...
render() {
return (
<ComposedComponent
{...this.props}
filterVarName={filterValue}
sortVarName={sortValue}
/>
);
}
}
}