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

Pass in React Component to Relay Variables and get a fragment from it. #775

Closed
ramsaylanier opened this issue Jan 28, 2016 · 13 comments
Closed

Comments

@ramsaylanier
Copy link

I'm querying a parent component that gets a "Layout" field, which contains a React Layout component. When the parent mounts, its goes through its query, gets the layout, and the updated its own Relay Variables to store the Component. It then renders the Component. But, there is currently no way to get a Fragment from the Component, since it technically doesn't exist when the query is first built.

Here's my parent component:

export default Relay.createContainer(WordpressPage, {

    initialVariables:{
        Component: null,
        page: null,
        showPosts: false,
        limit: 5
    },

    prepareVariables(prevVars){
        return{
            ...prevVars,
            showPosts: true
        }
    },

   fragments: {
     viewer: ({Component, showPosts, limit}) => Relay.QL`
       fragment on User {
          ${Component.getFragment("viewer", {limit:limit}).if(showPosts)},
          page(post_name:$page){
             id,
             post_title,
             post_type,
             post_content,
             thumbnail,
             layout{
                 meta_value
             }
           }
         }
     `,
   },
});

As you can see, the parent gets the layout field, it goes through a ShouldComponentUpdate check, and then calls a function that updates Relay Variables.

@josephsavona
Copy link
Contributor

Thanks for your patience. Note that the variables passed to fragment functions are opaque descriptors of the variables. In: viewer: ({Component, showPosts, limit}) => Relay.QL..., the Component isn't the actual component value.

An alternative approach here might be a container constructor:

export function createWordpressPage(Component) {
  class WordpressPage extends React.Component {
    ...

   render() {
    return <Component ... />; 
   }
  }

  return Relay.createContainer(WordpressPage, {
    initialVariables:{
      // remove `Component`
      ...
    },

    prepareVariables(prevVars){ ... },

    fragments: {
      viewer: ({showPosts, limit}) => Relay.QL`
        fragment on User {
          ${Component.getFragment("viewer", {limit:limit}).if(showPosts)},
          page(post_name:$page){
            ...
          }
        }
     `,
    },
  });
}

@ramsaylanier
Copy link
Author

Yeah that's actually the route I went after realizing my initial approach wasn't possible. Thanks!

@josephsavona
Copy link
Contributor

@ramsaylanier great, glad you got it working!

@ramsaylanier
Copy link
Author

@josephsavona sorry I realized that there is another issue.

I'm calling the createWordpressPage function to create the component using react router. This is what the routes look like:

function setLayout(nextState, replaceState){
  const {page} = nextState.params;
  const Layout = Layouts[page] ? Layouts[page] : Layouts['Default'];
  return this.component = createWordPressPage(Layout);
}

let routes = (
  <Route
    path="/" component={App}
    queries={AppQueries}
  >
    <IndexRoute
      component={LandingPage}
      queries={AppQueries}
    />

    <Route
      path=":page"
      onEnter={setLayout}
      queries={AppQueries}
    />
    <Route
      path="post/:post"
      component={PostSingle}
      queries={AppQueries}
    />
  </Route>
);

This works, but I think its creating a new component everytime I switch between :page routes —For example, when I got to '/articles' and then to '/about'. When I switch back I get:

Expected prop `viewer` supplied to `WordpressPage` to be data fetched by Relay. This is likely an error unless you are purposely passing in mock data that conforms to the shape of this component's fragment.

@ramsaylanier ramsaylanier reopened this Feb 8, 2016
@josephsavona
Copy link
Contributor

@ramsaylanier sorry for the delay getting back to you. have you tried memoizing the results of the createWordpressPage function? it would definitely be wasteful to reallocate a component class on every transition.

@ramsaylanier
Copy link
Author

I'm actually trying to go a different route based on the solution here: http://stackoverflow.com/a/35471007/2168061

Still having some issues though as noted in the comments.

@ramsaylanier
Copy link
Author

@josephsavona Basically, the problem I'm running into is that I'm using part of the initial query to determine the layout - so the first time it runs through and renders the correct component, it doesn't pick up the fragment.

In a Page component I do this:

_setLayout(){
    const Layout = this.props.viewer.page.layout.meta_value || 'DefaultLayout';
    const isDefault = Layout === 'DefaultLayout';
    const isPostList = Layout === 'PostList';

    this.props.relay.setVariables({
      page: this.props.page,
      isDefault: isDefault,
      isPostList: isPostList
    })
  }

  componentWillMount(){
    this._setLayout()
  }

and the Page Container:

const COMPONENTS = [
  [DefaultLayout, 'isDefault'],
  [PostList, 'isPostList']
];

export default Relay.createContainer(Page, {

  initialVariables: {
    page: null,
    isDefault: false,
    isPostList: false
  },

  fragments: {
    viewer: (variables) => Relay.QL`
    fragment on User {
      ${COMPONENTS.map( ([Component, layout]) => {
        console.log('mapping');
        const condition = variables[layout];
        return Component
          .getFragment('viewer', {page: variables.page})
          .if(condition)
      })},
      page(post_name:$page) {
        id
        layout{
          id
          meta_value
        }
      },
      settings{
        id
        uploads
        amazonS3
      }
    }
    `
  }
});

But this gives me an

Expected prop `viewer` supplied to `PostList` to be data fetched by Relay.

@josephsavona
Copy link
Contributor

What is the render function of Page? It looks like what's happening is that the variables aren't updating such that the if(condition) returns false even after you'd expect it to update. Can you try calling setVariables on a transition?

@ramsaylanier
Copy link
Author

class Page extends React.Component{

  _setLayout(){
    const Layout = this.props.viewer.page.layout.meta_value || 'DefaultLayout';
    const isDefault = Layout === 'DefaultLayout';
    const isPostList = Layout === 'PostList';

    this.props.relay.setVariables({
      page: this.props.page,
      isDefault: isDefault,
      isPostList: isPostList
    })
  }

  componentWillMount(){
    this._setLayout()
  }

    componentDidMount(){
        let animation = this.props.animation || PageAnimations.animateIn;
        AnimateItem(this._page, PageAnimations.animateIn);
    }

    render(){
        const { viewer, className } = this.props;
    const { page } = viewer;
    const Layout = Layouts[page.layout.meta_value];

    return(
            <div ref={ (c) => this._page = c } className={styles.base + ' ' + className}>
        <Layout.Component viewer={viewer} page={page} layout={Layout}/>
            </div>
        )
    }
}

@shaimo
Copy link

shaimo commented Mar 26, 2016

Was there ever a solution to this? I have the same use case and am not sure how to overcome this...

@ramsaylanier
Copy link
Author

I still haven't found a way to resolve this. 😢

@josephsavona
Copy link
Contributor

Thanks for your patience on this issue. We've looked into it several times - I tried to repro, @steveluscher tried to repro, we hacked on it together - and it seems like there is a gap between our understanding of the problem and your actual use case (what we tried worked).

I understand that it's a bit time consuming, but it would help to have a clear repro of the issue. Either a full Relay playground link, or a failing unit test, would help us clarify exactly what's happening and determine a solution.

@wincent
Copy link
Contributor

wincent commented Jan 30, 2017

(Spring cleaning.) This one is nearly a year old now, so closing due to staleness.

Thanks for filing the issue, and if you think it still exists in current Relay, please do send a repro so that we can investigate again.

@wincent wincent closed this as completed Jan 30, 2017
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