Apollo React Nested Query & Mutation - reactjs

I have a component that I'd like to fetch data for a profile and allow posting messages on the same view. I've created a simple profile component here, and have the mutation set within the query, similar to the Apollo React tutorial.
When I run this, I get a properly rendered query with data. When the button is pressed, the mutation occurs but the page re-renders with an empty object in the Query's data parameter and the page errors with Cannot read property 'name' of undefined (which is expected given a blank data object).
Is there a better approach here?
const GET_PROFILE = gql`
query GetProfileQuery($profileId: ID!) {
getProfileInfo(profileId: $profileId) {
name
}
}
`;
const ADD_ENDORSEMENT = gql`
mutation AddEndorsement($profileId: ID!, $body: String!) {
addEndorsement(profileId: $profileId, body: $body) {
endorsementId
}
}
`;
const Profile = props => {
const profileId = props.match.params.profileId;
return (
<Query query={GET_PROFILE} variables={{ profileId: profileId }}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return (
<Fragment>
<h1>Welcome {data.getProfileInfo.name}</h1>
<Mutation mutation={ADD_ENDORSEMENT}>
{(addEndorsement, { loading, error }) => (
<div>
<button
onClick={e => {
addEndorsement({
variables: {
profileId: profileId,
body: "This is a test."
}
});
}}
>
Save
</button>
{loading && <p>Loading...</p>}
{error && <p>Error :(</p>}
</div>
)}
</Mutation>
</Fragment>
);
}}
</Query>
);
};

I had a similar issue were data turned up undefined. In my case the problem was caused by a Query subscribeToMore option I was using invalidating the cache by silently failing to merge the updated data into the original Query results (it did not contain exactly identical data fields).
The question doesn't mention subscriptions, but it will be a similar cache invalidation problem.
To fix I'd first try making sure the Query and Mutation's return identical data.
If that doesn't resolve, then depending on responsiveness needs either look at the "refetchQueries/awaitRefetchQueries" to refresh the Query, or manually poking the updated data into the local cache with "update" options on the Mutation.
Good luck.

Try adding the following to the onClick mutation, it should help avoid needing to update the cache manually, although that would be the next step. You can pass just the string name of the query to refetchQueries, you don't need to pass the actual query there. Another trick is to ensure you are getting the same fields back so the cache object matches, (not sure what type you are returning on the query/mutation.
onClick={e => {
addEndorsement({
variables: {
profileId: profileId,
body: 'This is a test.',
},
refetchQueries: ['GetProfileQuery']
})
}}

Related

React/Apollo fetchMore reloading the whole page

I have a problem trying to do a pagination / fetchMore button for a list of data on my webapp. My problem is that when I click on the fetchMore button the whole page is reloading, old school style, while I would like only the updated list of 'calculs' to be reloaded but not the rest of the query (users...).
Is there a way to limit what is updated after a fetchMore ? Don't want the whole DOM to be rerendered.
I also tried to send a specific query, only refecthing what I need, but I got an error as well.
Here is the needed part of the code:
export const query = gql`
query UserAndData($limit: Int!) {
user {
_id,
nom,
prenom,
email,
isAdmin
}
adminCalculations {
_id,
lastUpdated,
comments
}
adminUsers {
_id,
lastConnect,
nom,
prenom,
email,
societe
}
calculs(limit: $limit) {
_id,
userId,
date,
duration,
user {
email
}
}
count
}
`
class Admin extends Component {
state = {
limit : 100,
}
render() {
return(
<Query query={query} variables={{ limit : this.state.limit}} fetchPolicy="cache-and-network">
{({ loading, error, data, fetchMore }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
console.log(data);
if (!data || (!data.user || !data.user.isAdmin)){
return (<Redirect to="/" />)
}
return(
<div className="admin">
(lot of coding and rendering users list, calculs lists, etc...)
<div className="bouton" onClick={() => {
fetchMore({
variables : { limit : this.state.limit + 100 },
updateQuery : (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, { calculs :
fetchMoreResult.calculs });
}
})
this.setState({ limit : this.state.limit + 100 })
}
}>Charger plus de calculs</div>
</div>
</div>
</div>
</div>
)
}}
</Query>
)
}
}
export default Admin;
Actually, I found out the problem was linked to my query. As I was querying the whole needed data for my Component and as I had a
if(loading)
statement, the whole page was loading again each time the refecthing was taking place and therefore the whole page was rendering the
<p>Loading...</p>
even if the loading time was very small -> that caused my full page "refreshed"
In order to solve this issue I have created a child component that include also an Apollo Query component but only the query that I want to be refetched. The parent component with the user, adminCalculations and adminUsers query is not pointlessly refreshed and only the needed child query refetches and rerenders.

refetching a query with react-apollo: why is `loading` not true?

I'm trying Apollo and using the following relevant code:
const withQuery = graphql(gql`
query ApolloQuery {
apolloQuery {
data
}
}
`);
export default withQuery(props => {
const {
data: { refetch, loading, apolloQuery },
} = props;
return (
<p>
<Button
variant="contained"
color="primary"
onClick={async () => { await refetch(); }}
>
Refresh
</Button>
{loading ? 'Loading...' : apolloQuery.data}
</p>
);
});
The server delays for 500ms before sending a response with { data: `Hello ${new Date()}` } as the payload. When I'm clicking the button, I expect to see Loading..., but instead the component still says Hello [date] and rerenders half a second later.
According to this, the networkStatus should be 4 (refetch), and thus loading should be true. Is my expectation wrong? Or is something regarding caching going on that is not mentioned in the React Apollo docs?
The project template I'm using uses SSR, so the initial query happens on the server; only refetching happens in the browser - just if that could make a difference.
I think that you need to specify notifyOnNetworkStatusChange: true in the query options (it's false by default).

Refetch queries after Mutation doesn't work

I have mutation as follows:
<Mutation
mutation={ADD_NEW_SLOT}
refetchQueries={() => [{ query: GET_COMPANY_ADDRESSES, variables: { companyId: this.props.session.company.id } }]}
awaitRefetchQueries={true}
>
.......
</Mutation>
Where GET_COMPANY_ADDRESSES is exported from a parent component.
But it doesn't refresh after the mutation is done.
What am I doing wrong?
UPDATE
The return of the render function in the parent component is as follows:
<Query query={GET_COMPANY_ADDRESSES} variables={{companyId: session.company.id}} notifyOnNetworkStatusChange={true} fetchPolicy={'cache-and-network'}>
{({loading, error, refetch, data}) => {
if (loading) return <LoadingIndicator/>;
if (error) return <ErrorIndicator description={error.message}/>;
const treeNodes = convertSlotsToTree(data);
const address = data.companyAddresses[1];
return (
<AddSlot address={address}
toggleSlotForm={this.props.togglePanel}
session={this.props.session}/>
)
}}
</Query>
The graphql query is in the same file and it is as follows:
export const GET_COMPANY_ADDRESSES = gql`
query CompanyAddresses($companyId: Int!) {
companyAddresses(companyId: $companyId) {
id
name
default
compound
address {
id
addressFull
countryCode
city
postCode
slotSet{
id
area
zone
aisle
side
level
position
disabled
col
printEntry
fullName
}
}
}
}
`;
It still does not work with react-apollo 3.1.x w/o workaround.
It seems that if you use these two things, updates will be sent to Query:
Set option for Query: fetchPolicy="cache-and-network" (you use this already).
Comment refetchQueries and use Query's refetch instead. Attach function to Mutation's onCompleted, and call refetch from the Query directly. You should see graphql query in HTTP requests, and UI should updates itself as well.
Note. "cache-first" as fetchPolicy did not work, although I guess cache is updated, but UI does not.
With Angular I found this working, as expected (note the "no-cache" flag):
this.postsQuery = this.apollo.watchQuery<any>({
query: GET_USER_ON_ROLE,
fetchPolicy: "no-cache",
variables: {
roleId : this.roleId
}
});
this.querySubscription = this.postsQuery
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.allUsers = data.getAllUsersPerRoleUI;
});
An you need to reload once the mutation is over using refetch():
public reloadFunction()
{
this.postsQuery.refetch()
}

Store single property from GraphQL query in local Apollo store

In my React app when a user goes to myapp.com/org/companyname it loads a page with the following logic to get some basic details about the company based on the company name in the URL:
const FIND_COMPANY = gql`
query CompanyQuery($companyName: String!) {
company(name: $companyName) {
... on Company {
name
description
}
... on Error {
errorMessage
}
}
}
`;
<Query
query={FIND_COMPANY}
variables={{
companyName: this.props.location.pathname.split("org/")[1]
}}
>
{({ data }) => {
if (data.company.errorMessage)
return <div>{data.company.errorMessage}</div>;
return (
<div>
<h1>{data.company.name}</h1>
<p>{data.company.description}</p>
</div>
);
}}
</Query>
This works fine, however I want to store just the currently company name in the local Apollo state so that it can be easily accessed by other components without having to do string manipulation on the URL in each component.
To do this I've followed the 'Direct writes' section of the Apollo local state docs and ended up with the following:
<ApolloConsumer>
{client => (
<Query
query={FIND_COMPANY}
variables={{
companyName: this.props.location.pathname.split("org/")[1]
}}
onCompleted={data =>
client.writeData({ data: { companyName: data.company.name } })
}
>
{({ data }) => {
if (data.company.errorMessage)
return <div>{data.company.errorMessage}</div>;
return (
<div>
<h1>{data.company.name}</h1>
<p>{data.company.description}</p>
</div>
);
}}
</Query>
}
</ApolloConsumer>
Where I write just the company name to the Apollo local state onCompleted. However, when I run this it crashes the app.
Is this the correct way to store local state in Apollo or have I misunderstood how local Apollo state should be used?

Refetch container refetches, but does not update the view

My application looks something like what's included in the snippets below.
(I've left out a lot of the implementation details in the hopes that the code below is sufficient to get my question across).
SampleApp.js
const SampleAppQuery = graphql`
list SampleAppQuery {
...ItemComponent_item
}
`
function SampleApp() {
return (
<QueryRenderer
environment={environment}
query={SampleAppQuery}
render={(error, props) => {
if (props) {
return <AppRefetchContainer list={props} />
}
}}
/>
)
}
AppRefetchContainer.js
class AppRefetchContainer extends Component {
render() {
<div>
<HeaderComponent refetchList={this._refetchList} />
{list.map(item => <ItemComponent item={item} />)}
</div>
}
_refetchList(params) {
this.props.relay.refetch(params)
}
}
export default createRefetchContainer(
AppRefetchContainer,
graphql`
fragment AppRefetchContainer_list on Item {
...ItemComponent_item
}
`,
graphql`
query AppRefetchContainerQuery($sortBy: String!)
list SampleAppQuery(sortBy: $sortBy) {
...ItemComponent_item
}
`
)
The initial application load is just fine. Clicking on one of the headers should trigger a refetch so that the list data can be sorted on the passed inparams. When I trigger a refetch, the server responds with the correct data, however, the component does not rerender.
I suspect that my issue is with the way I've defined the fragments here. The console error I get on initial page load is:
RelayModernSelector: Expected object to contain data for fragment
`AppRefetchContainer_list`, got `{"list":[{"__fragments":
{"ItemComponent_item":{}},"__id":"[some_valid_id]","__fragmentOwner":null},{...
Question(s):
I'm not entirely sure how to interpret that error. Is it the reason why I'm unable to rerender the UI?
I know this approach may not be ideal, but how do I refetch from a component that isn't necessarily a fragment of the query?
Happy to provide any clarification or missing context :)

Resources