react-relay-network-modern-ssr
react-relay-network-modern-ssr copied to clipboard
Components with createFragmentContainer not resolving fields
When I have a component with a createFragmentContainer
HoC, on the server side I get passed props to that component that look similar to this:
{ id: 'Qm9vay0x',
__fragments: { User_viewer: {} },
__id: 'Qm9vay0x',
__fragmentOwner:
{ fragment: { dataID: 'client:root', node: [Object], variables: {} },
node:
{ kind: 'Request',
fragment: [Object],
operation: [Object],
params: [Object],
hash: '7f3a8b6ed33bc16971bcdc4704b94b0e' },
root: { dataID: 'client:root', node: [Object], variables: {} },
variables: {} }
}
When I want the props to look something like this (which is what the client gets):
{
id: "Qm9vay0x",
name: "Jim Halpert"
}
I'm following this example: https://github.com/zeit/next.js/tree/master/examples/with-react-relay-network-modern, with one change in _app.js
: I'm getting the queryID in the render function by Component.query().default.params.name
instead of Component.query().params.name
. Here are my dependencies:
"react-relay": "^5.0.0",
"react-relay-network-modern": "^4.0.4",
"react-relay-network-modern-ssr": "^1.2.4",
My theory is that when rendered on the server side, createFragmentContainer
tries to render the component with the exact props it was passed with that contains all the relay metadata, instead of the data that the fragment defines.
+1 @HsuTing this also means that refetchContainer and paginationContainer don't work when following your next.js example.
I'm just trying to render a simple blog page:
import React from "react"
import Relay, { graphql } from "react-relay"
const fragment = graphql`
fragment blog_pages on Query
@argumentDefinitions(
count: { type: "Int", defaultValue: 10 }
cursor: { type: "String" }
) {
blogPages(first: $count, after: $cursor)
@connection(key: "blog_blogPages") {
edges {
node {
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`
const query = graphql`
query blog_BlogIndexPageQuery($count: Int!, $cursor: String) {
...blog_pages @arguments(count: $count, cursor: $cursor)
}
`
class TestDiv extends React.Component {
render() {
console.log(this.props)
return <div />
}
}
const FragmentContainer = Relay.createFragmentContainer(TestDiv, {
pages: fragment
})
FragmentContainer.query = query
FragmentContainer.getInitialProps = async ctx => {
return {
variables: {
count: 10,
cursor: null
}
}
}
export default FragmentContainer
but on both the server and client i'm getting the the warning:
Warning: createFragmentSpecResolver: Expected prop `pages` to be supplied to `Relay(TestDiv)`, but got `undefined`. Pass an explicit `null` if this is intentional.
and the props:
{
__fragments: { blog_pages: { count: 10, cursor: null } },
__id: 'client:root',
__fragmentOwner: {
identifier: 'query blog_BlogIndexPageQuery(\n' +
' $count: Int!\n' +
' $cursor: String\n' +
') {\n' +
' ...blog_pages_1G22uz\n' +
'}\n' +
'\n' +
'fragment blog_pages_1G22uz on Query {\n' +
' blogPages(first: $count, after: $cursor) {\n' +
' edges {\n' +
' node {\n' +
' title\n' +
' id\n' +
' __typename\n' +
' }\n' +
' cursor\n' +
' }\n' +
' pageInfo {\n' +
' hasNextPage\n' +
' endCursor\n' +
' }\n' +
' }\n' +
'}\n' +
'{"count":10,"cursor":null}',
node: {
kind: 'Request',
fragment: [Object],
operation: [Object],
params: [Object],
hash: '97997107df67905d085d233d494694d7'
},
variables: { count: 10, cursor: null }
},
pages: null,
relay: {
environment: RelayModernEnvironment {
configName: undefined,
__log: [Function: emptyFunction],
_defaultRenderPolicy: 'full',
_operationLoader: undefined,
_operationExecutions: Map(0) {},
_network: [Object],
_getDataID: [Function: defaultGetDataID],
_publishQueue: [RelayPublishQueue],
_scheduler: null,
_store: [RelayModernStore],
options: undefined,
__setNet: [Function (anonymous)],
DEBUG_inspect: [Function (anonymous)],
_missingFieldHandlers: undefined,
_operationTracker: [RelayOperationTracker]
}
}
}
Any ideas on how to deal with this?
To follow on from that, if I just do the query without the fragment it works fine:
import React from "react"
import BlogIndexPageQuery from "shared-js/queries/BlogIndexPageQuery"
class TestDiv extends React.Component {
render() {
console.log(this.props)
return <div />
}
}
TestDiv.query = graphql`
query BlogIndexPageQuery {
blogPages {
edges {
node {
title
body
}
}
}
}
`
export default TestDiv
@juhaelee i just worked out the issue. You need to wrap your fragmentContainer in a higher order component which passes in the props as the expected fragment. its not an issue with the library. Please see how I achieved this below:
import React from "react"
import BlogIndexPageQuery from "shared-js/queries/BlogIndexPageQuery"
import { createPaginationContainer, graphql } from "react-relay"
class TestDiv extends React.Component {
render() {
console.log(this.props)
return <div />
}
}
const query = graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query blogPageIndexQuery($count: Int!, $cursor: String) {
...blog_pages @arguments(count: $count, cursor: $cursor)
}
`
const TestPagContainer = createPaginationContainer(
TestDiv,
{
pages: graphql`
fragment blog_pages on Query
@argumentDefinitions(
count: { type: "Int", defaultValue: 10 }
cursor: { type: "String" }
) {
blogPages(first: $count, after: $cursor)
@connection(key: "blog_blogPages") {
edges {
node {
title
}
}
}
}
`
},
{
direction: "forward",
getConnectionFromProps(props) {
return props.pages && props.pages.blogPages
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount
}
},
getVariables(props, { count, cursor }, fragmentVariables) {
return {
count,
cursor
}
},
query: query
}
)
const PagWrapper = props => (
<TestPagContainer
pages={props}
// user={this.props.user}
// // clientPaymentToken={this.props.clientPaymentToken}
/>
)
PagWrapper.query = query
PagWrapper.getInitialProps = async () => {
return {
variables: {
count: 10,
cursor: null
}
}
}
export default PagWrapper
FWIW this may only be required when following https://github.com/zeit/next.js/tree/master/examples/with-react-relay-network-modern
@stan-sack You should modify the _app.js
, depending on your actual case. _app.js
will give the all data to the props
. In your case, a prop
named pages
is required in createFragmentContainer
, but the props
will look like { blogPages: { ... } }
in blog.js
.
Maybe you can try this:
// _app.js
...
return (
<QueryRenderer
environment={environment}
query={Component.query}
variables={variables}
render={({ error, props }) => {
if (error) return <div>{error.message}</div>
else if (props) return <Component pages={props} />
return <div>Loading</div>
}}
/>
);
...
@juhaelee Is the problem resolved? Maybe you can give the reproduce repo?
Was actually stuck on this for quite some time, and the above explanation didn't really help.
Found this StackOverflow article which describes the same problem and reasoning why it returns a weird payload instead of the data. https://stackoverflow.com/questions/52145389/relay-queryrenderer-does-not-return-expected-props-from-query
Relay encapsulates data by fragments. You can see data only if they are included in current component fragment (see https://facebook.github.io/relay/docs/en/thinking-in-relay#data-masking)
Basically, if you pass that reference to a child which has a createFragmentContainer
, the data will be loaded and passed through.