relay-modern:get current fragment variables

Created on 30 May 2017  路  16Comments  路  Source: facebook/relay

previously I can do a this.props.relay.variabes to get the current variables;
but now, it seems relay-modern doesn't expose variables to components. what is the recommended way of retrieving current query variables?

the todo-modern is a bit too simple, can we have a more sophisticated example that can show case most new features?

Most helpful comment

This would still solve a considerable problem for us - currently needing to track a fragment variable (the date of the current view) with a separate copy in the UI, which of course creates opportunities for bugs if the two values ever desync.

All 16 comments

I extended the todo-modern example by adding a 'switch view' button, whereby user can click to switch to a different view todo2. The todo2 component will query and render some extra data I added in schema.

here is the source code of Todolist, which is now a refetchContainer

export default createRefetchContainer(TodoList,
  graphql.experimental`
    fragment TodoList_viewer on User
        @argumentDefinitions( 
          isNormalView:{
            type:"Boolean!",
            defaultValue:true
           }
        )
          {
          todos(
            first: 2147483647  # max GraphQLInt
          ) @connection(key: "TodoList_todos") {
            edges {
              node {
                id,
                complete,
                ...Todo_todo  @include(if: $isNormalView),
                ...Todo2_todo @skip(if: $isNormalView),
              },
            },
          }
        }
  `,
  graphql.experimental`
  query TodoListViewRefetchQuery($isNormalView: Boolean!){
    viewer{
      user{
           ...TodoList_viewer @arguments(isNormalView: $isNormalView) 
        }
      }
  }`
);

now the problem is I don't know whether I'm in normalView or not...

  _onSwitchView = e => {
    this.props.relay.refetch(previousVars => ({ isNormalView: !previousVars.isNormalView }), null)
  }

   render(){
        const isNormalView  = ???
       <div>
          { this.props.viewer.todos.edges.map(edge => isNormalView?<Todo... > :<Todo2 ...>)}
      </div>
  }

in my render function, how do I know whether I should render todo or todo2?

yes I can use state to store the variables. but then it will come with another problem: you update your state and your component will get re-rendered immediately; but relay might be still busy with fetching data... (equivalent of getpendingVariables stuff which we don't have now)
so what's the best practice?

@yuzhi and I had a discussion about this last week and Yuzhi actually has a PR to expose props.relay.variables ready. We had a concern that that added runtime overhead for adding a feature that most of the time has a better answer.

For example, if the top level query is

query TodoDetailsQuery {
  node(id: $todoID) {
    ...TodoComponent
  }
}

The best way for TodoComponent to get the id of the Todo is to have a fragment with id instead of looking at props.relay.variables.todoID because the fragment is more universal and can be used inside other root queries.

Your case is a bit different and interesting. As you said, using state on the outer component combined with the callback would be possible, but also a tedious and error prone.

@yuzhi: Thoughts on this? Maybe we could make .variables lazy by having props.relay.getVariables() that would at least remove the runtime cost.

We have essentially the same issue, where our view is showing a certain date; that date needs to be displayed in the view, and it needs to be used in the React query as well to fetch the correct data from the server. We have another State variable instantiated for this which duplicates the Date state, but of course this seems hacky.

Can getFragmentVariables cache so that runtime cost is incurred only once (as long as variables haven't changed)? That seems ideal but at least at this point we would take any implementation.

@lukecwilliams the version I had would cache it and only recalculate when the component gets new props.

@kassens Making it lazy is definitely doable, but might actually make it more complex since we might have to keep track of when it's stale to figure out if it needs to be recalculated, or are you thinking about recalculating each time getVariables() is called.

just found a new way while I am on the train...

This can be upgraded by conditionally rendering a QueryRenderer which will load the data once it is rendered. The code overhead of doing this is dramatically reduced with the new API.

I think it sound like a neat solution...

on a second thought, while this could be a neat solution ans is the recommended way in official document. The change would dramatic. I don't think our classic relay project can afford changes big as this

follow ups
use QueryRenderer can solve the problem
https://github.com/facebook/relay/issues/1792#issuecomment-307003860

@yuzhi I think we have a __localVariables prop defined in refetchContainers, but isn't exposed to its child. Is __localVariables the same as what you guys are expecting from getVariables()? Can we simpley expose it to child?

Not exactly. __localVariables is essentially null most of the time unless a refetch has happened, so you won't actually know what it was before then. However, refetchContainer's refetch function does take a function to calculate the new variables, based on the existing variables. That might help with some of the use cases of variables.

can we set __localvariable during component initialization so that it will.always have a value?

Having a props.relay.getVariables() that is called explicitly seems like a good compromise. The only caveat would be that the component should re-render when the results would change, this should be doable (maybe only if the function has been called?).

I think the biggest challenge is that we never update fragmentVaraibles.... definition.defaultValue always prevail

//  relay\packages\relay-runtime\store\RelayConcreteVariables.js
function getFragmentVariables(
  fragment: ConcreteFragment,
  rootVariables: Variables,
  argumentVariables: Variables,
): Variables {
  let variables;
  fragment.argumentDefinitions.forEach(definition => {
    if (argumentVariables.hasOwnProperty(definition.name)) {
      return;
    }
    variables = variables || {...argumentVariables};
    switch (definition.kind) {
      case 'LocalArgument':
        variables[definition.name] = definition.defaultValue; // !!!this will always use the default argmentu value
        break;

I created PR https://github.com/facebook/relay/pull/1838 to address this problem
I have also tested it on my local environment and it seems to work fine...

let me know if the change makes sense to you guys or not. thanks!

Could you use the variables that are stored in React context? This would not work for refetch containers or their children as published now because of https://github.com/facebook/relay/issues/1695, but it would if https://github.com/facebook/relay/pull/1702 is merged.

If that PR will be merged soon, maybe another possible approach is a higher order component? You can pass it your fragment reference and it would read the variables from context, calculate using getVariablesFromObject from relay-runtime, and pass it to your component as a prop. This HOC should also work for fragment containers too.

Thanks for the input here. We're currently going through old issues that appear to have gone stale (ie. not updated in about the last 3 months) because the volume of material in the issue tracker is becoming hard to manage. If this is still important to you please comment and we'll re-open this.

Thanks once again!

This would still solve a considerable problem for us - currently needing to track a fragment variable (the date of the current view) with a separate copy in the UI, which of course creates opportunities for bugs if the two values ever desync.

This discussion should be reopened, currently we need to maintain two copies of filter variables if we want to reuse current set of variables in relay.refetch

it could be solved by this one https://github.com/facebook/relay/pull/2507

Was this page helpful?
0 / 5 - 0 ratings