I'm using React Router v2.8.1 and have the following simple setup for my app:
ReactDOM.render(
<Router history={browserHistory}>
<Route path='/' component={App} />
<Route path='/create' component={CreatePage} />
</Router>
, document.getElementById('root')
)
Both App and CreatePage have as a root a QueryRenderer.
Appconst AppAllPostQuery = graphql`
query AppAllPostQuery {
viewer {
...ListPage_viewer
}
}
`
class App extends Component {
render() {
return (
<QueryRenderer
environment={environment}
query={AppAllPostQuery}
render={({error, props}) => {
if (error) {
return <div>{error.message}</div>
} else if (props) {
return <ListPage viewer={props.viewer} />
}
return <div>Loading</div>
}}
/>
)
}
}
ListPage is a FragmentContainer that renders a list of Posts. Here's how the fragment containers for ListPage and Post are defined with their fragments:
ListPageexport default createFragmentContainer(ListPage, graphql`
fragment ListPage_viewer on Viewer {
...Post_viewer
allPosts(last: 100, orderBy: createdAt_DESC) @connection(key: "ListPage_allPosts", filters: []) {
edges {
node {
id
description
imageUrl
...Post_post
}
}
}
}
`)
Postexport default createFragmentContainer(Post, graphql`
fragment Post_viewer on Viewer {
id
}
fragment Post_post on Post {
id
description
imageUrl
}
`)
CreatePageconst CreatePageViewerQuery = graphql`
query CreatePageViewerQuery {
viewer {
id
}
}
`
class CreatePage extends React.Component {
state = {
description: '',
imageUrl: '',
}
render () {
return (
<QueryRenderer
environment={environment}
query={CreatePageViewerQuery}
render={({error, props}) => {
if (error) {
return <div>{error.message}</div>
} else if (props) {
return (
<div className='w-100 pa4 flex justify-center'>
// ...
</div>
)
}
return <div>Loading</div>
}}
/>
)
}
_handlePost = (viewerId) => {
const {description, imageUrl} = this.state
console.log(`New Post: `, viewerId)
CreatePostMutation(description, imageUrl, viewerId, () => this.props.router.replace('/'))
}
}
Now, when I'm deleting a post and use the updater, everything works as expected and the deleted post gets removed from the store:
updater: (proxyStore) => {
const deletePostField = proxyStore.getRootField('deletePost')
const deletedId = deletePostField.getValue('deletedId')
const viewerProxy = proxyStore.get(viewerId)
const connection = ConnectionHandler.getConnection(viewerProxy, 'ListPage_allPosts')
ConnectionHandler.deleteNode(connection, deletedId)
}
However, when adding a new post, the ConnectionHandler can't seem to find the connection with key ListPage_allPosts:
updater: (proxyStore) => {
const createPostField = proxyStore.getRootField('createPost')
const newPost = createPostField.getLinkedRecord('post')
const viewerProxy = proxyStore.get(viewerId)
const connection = ConnectionHandler.getConnection(viewerProxy, 'ListPage_allPosts')
// `connection` is undefined
ConnectionHandler.insertEdgeAfter(connection, newPost)
}
Since connection is undefined here, I'm getting the following error message in the next line:
RelayConnectionHandler.js:225 Uncaught (in promise) TypeError: Cannot read property 'getLinkedRecords' of undefined
My assumption is that connection can't be found here because I have _two different_ QueryRenderers - and the one that I'm using in this case (unlike the one for the delete-mutation) doesn't have the ListPage_allPosts in its tree. Is that correct?
What would be a good way to solve this issue? Can connections be shared across QueryRenderers? Is there a way how this can be solved in combination with React Router?
Works fine for me. Make sure you specify filters when you call ConnectionHandler.getConnection.
BTW – don't nest <QueryRenderer>s like that unless you want to hurt UX by waterfalling your loads.
I'm not backporting any Relay Modern stuff for react-router-relay, but I more or less have it ready for Found Relay.
Thanks a lot for your reply!
Make sure you specify filters when you call
ConnectionHandler.getConnection.
Tried that with const connection = ConnectionHandler.getConnection(viewerProxy, 'ListPage_allPosts', []) but connection still is undefined.
BTW – don't nest
<QueryRenderer>s like that unless you want to hurt UX by waterfalling your loads.
Maybe I'm wrong, but I don't think my QueryRenderers are nested?! I've got two of them and both are the root for a specific route. Does that still count as nesting and should be avoided?
I uploaded the project here: https://github.com/nikolasburk/relay-modern-instagram Would be awesome if someone could take a look. I'm pretty lost and don't know how I can further debug this.
Note that there is an issue with the latest Relay Modern release and Graphcool - Relay expects the viewer type not to have an id, but Graphcool generates one. This is fixed on Relay master. As a workaround, try deleting the definition of the Viewer type id field in your schema.graphql file.
I'm still trying to wrap my head around lots of things, but deleting the viewer's id would also break my current code (I think) since I'm using the viewerId when deleting posts with the updater (which does work):
updater: (proxyStore) => {
const deletePostField = proxyStore.getRootField('deletePost')
const deletedId = deletePostField.getValue('deletedId')
const viewerProxy = proxyStore.get(viewerId)
const connection = ConnectionHandler.getConnection(viewerProxy, 'ListPage_allPosts')
ConnectionHandler.deleteNode(connection, deletedId)
}
By the way, I tried to implement the mutations according to this example. In particular, the mutation for removing todos uses a user which I thought would be somewhat equivalent to the viewer.
@nikolasburk Actually this is probably just the GC – the connection gets removed from the store when you go from <App> to <CreatePage>, so connection is correctly undefined, because it's not in the store.
And I misunderstood your query renderer tree. You will hit waterfall problems if you _do_ nest them, though :p
What's happening here is that, out-of-the-box, Relay Modern doesn't cache anything that isn't being rendered.
When you go to <CreatePage>, you unmount <App>, and the GC will expunge the connection from the store, so getConnection will correctly return undefined.
Since in this case, the connection isn't in the local store any more, you don't need to update it. The correct way to handle this is to just do:
if (connection) {
ConnectionHandler.insertEdgeAfter(connection, newPost);
}
In other words, only update the store if there's anything to update.
I'm running into this issue but I'm not changing the route or render container. I'm deleting an item. I do have a nested <QueryRenderer> because I have a special case for the UI where I think it is necessary. The connection is undefined, but I'm at a loss as to why. Are there any utilities for inspecting what connections the cache knows about. Or any other debugging tools for this kind of problem?
Hello,
I got same error. Did you solved it?
https://github.com/facebook/relay/issues/2163
This is error.
@taion i see you said:
BTW – don't nest
s like that unless you want to hurt UX by waterfalling your loads.
But say that you do want to nest queryrenderers because you want to fetch additional data after the page as actually loaded. Is this a bad practice? Should i use something like refetchContainer instead with its inital query being a small amount of data while showing a loading component initially, then on componentdidmount call refetch()?
you can use @include instead of another QueryRenderer
@sibelius What if I want to use QueryRenderer though. For example, I want to use a QueryRenderer to lazily fetch additional data as said in the docs:
As React components, QueryRenderers can be rendered anywhere that a React component can be rendered, i.e. not just at the top level but within other components or containers; for example, to lazily fetch additional data for a popover.
I use a QueryRenderer as a parent to load the inital content. Then an inner QueryRenderer after to load additional data on mount. However, I get a strange error when doing this: Here is a stack overflow link to the error I'm getting
I recommend avoiding QueryRenderer inside QueryRenderer, the top one will rerender the below one in the tree causing another fetch
That makes sense.
@sibelius
However, I'm still wondering, is there a way for a parent QueryRender to not be affected by the inner QueryRender? I'm assuming what's happening here that when the inner QueryRenderer's mounts and fetches data that blows away the parent's fetched data, and the parent renderer gets passed the inner renderer's data.
@juhaelee your question reminds me this: https://github.com/facebook/relay/issues/2100
@jgcmarins Seems like that issue is similar but opposite of the problem I am having. That one, the inner child refetch gets disposed, however, what I am seeing is that with nested QueryRenderer's with the same root query, the inner child query does get executed, but the inner QueryRenderer blows out my store and removes the data that was originally fetched and needed from the outer QueryRenderer
There are two things possibly at play here, one is #2237 which is a bug that can occur whether or not you have nested QueryRenderers, which is
The other is a problem specific to nested QueryRenderers, and is something I'm looking for in the issues (and is why I stumbled across yours here). Because relay uses react context to propagate info (variables used, etc) from QueryRenderer to FragmentContainer, your inner query is stomping out the context of your outer one, so all FragmentContainers try to use the context of the inner one, even if its data comes from the outer one.
This may also be related to #1792 and #1986.
@mike-marcacci I've run into the nested QueryRenderer issue too. One thing we've done is replace the inner QueryRenderer with a RefetchContainer and make use of the renderVariables you can pass to make sure the lower components have the correct set of variables in React context. It might be helpful if QueryRenderer's were able to take a renderVariable prop as well.
@robrichard thanks for the tip! That was one of the possible workarounds on my list, but it presents its own challenge: when variable values are derived from something up-scope (such as from the URL), it becomes very cumbersome to set these values. I may give this a go nonetheless.
I'm have a similar issue with React Router v4
RootRouter.js
<Switch>
<Route path="/list" component={ListPage}/>
<Route path="/create" component={CreatePage}/>
</Switch>
ListPage.js use follow query
fragment ListPage_viewer on Viewer {
list(...) @connection(key: "ListPage_list") {
edges {
node {
id
...
}
}
}
}
The scenario is as follows.
/list and click /create link/create location call CreateItemMutation mutationCreateItemMutation mutation updater can't update ListPage_list because ListPage unmounthistory.push("/list") in onCompleted and return to the /list page, showing only old dataHow can i solve this issue?
Should i set cacheConfig to { force: true } or use Subscriptions?
@juhaelee I'm interested to know how you went about this? Did you end up nesting the queryrenderer OR was there another way around this?
@sibelius What would you recommend if nesting queryrenderers is not a good practise?
Most helpful comment
What's happening here is that, out-of-the-box, Relay Modern doesn't cache anything that isn't being rendered.
When you go to
<CreatePage>, you unmount<App>, and the GC will expunge the connection from the store, sogetConnectionwill correctly returnundefined.Since in this case, the connection isn't in the local store any more, you don't need to update it. The correct way to handle this is to just do:
In other words, only update the store if there's anything to update.