Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components with createFragmentContainer not resolving fields #16

Open
juhaelee opened this issue Aug 14, 2019 · 7 comments
Open

Components with createFragmentContainer not resolving fields #16

juhaelee opened this issue Aug 14, 2019 · 7 comments

Comments

@juhaelee
Copy link

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.

@stan-sack
Copy link

stan-sack commented Feb 27, 2020

+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?

@stan-sack
Copy link

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

@stan-sack
Copy link

@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

@stan-sack
Copy link

FWIW this may only be required when following https://github.com/zeit/next.js/tree/master/examples/with-react-relay-network-modern

@HsuTing
Copy link
Contributor

HsuTing commented Feb 29, 2020

@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>
        }}
      />
  );
...

@HsuTing
Copy link
Contributor

HsuTing commented Feb 29, 2020

@juhaelee Is the problem resolved? Maybe you can give the reproduce repo?

@Sicria
Copy link

Sicria commented Mar 20, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants