React/Apollo fetchMore reloading the whole page - reactjs

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.

Related

useQuery not returning up to date data

i have a homepage (/home) with a list of products as cards (retrieved
via useQuery) each of which has an upvote button
when I click upvote,
i trigger a mutation to upvote + a UI change to update the vote
count
when i go to another page, and then go back to /home,
useQuery doesn’t retrieve the products with the correct vote count
however, when I check my DB, the products all have the correct vote
count.
Why doesuseQuery not return the right amount until i do another page
refresh?
for reference, here it is below:
const Home = props => {
const {data, loading, error} = useQuery(GET_PRODUCTS_LOGGED_IN, {
variables: {
userid: props.userid
}
});
console.log(
'data', data.products // this data is outdated after I go from /home -> /profile -> /home
);
return (
<Container>
{_.map(data.products, product => (
<VoteButton
hasVoted={product.hasVoted}
likes={product.likes}
productid={product.productid}
/>
))}
</Container>
);
}
const VoteButton = ({likes, hasVoted, productid}) => {
const [localHasVoted, updateLocalHasVoted] = useState(hasVoted);
const [likesCount, updateLikesCount] = useState(likes);
const [onVote] = useMutation(VOTE_PRODUCT);
const onClickUpvote = (event) => {
onVote({
variables: {
productid
}
})
updateLocalHasVoted(!localHasVoted);
updateLikesCount(localHasVoted ? likesCount - 1 : likesCount + 1);
}
return (
<VoteContainer onClick={onClickUpvote}>
<VoteCount >{likesCount}</VoteCount>
</VoteContainer>
);
};
On your useQuery call, you can actually pass it a config option called 'fetch-policy' which tells Apollo how you want the query to execute between making the call or using the cache. You can find more information here, Apollo fetch policy options.
A quick solution would be be setting fetch-policy to cache and network like the the example below.
const {data, loading, error} = useQuery(GET_PRODUCTS_LOGGED_IN, {
variables: {
userid: props.userid
},
fetchPolicy: 'cache-and-network',
});
You can also make it so that when your mutation happens, it will run your query again by setting the 'refetch-queries' option on useMutation like the code below.
This will cause your query to trigger right after the mutation happens.
You can read more about it here Apollo mutation options
const [onVote] = useMutation(VOTE_PRODUCT, {
refetchQueries: [ {query: GET_PRODUCTS_LOGGED_IN } ],
});

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?

React GraphQL - How to return the results of a Query component as an object to use in React context

I have a Query component which gets the information of the a user who has logged in. The query itself works but I am having trouble returning the results of the query as an object which I then want to pass to React.createContext()
I am using Apollo client to make my Query component in my React application.
The following is an example of my current code:
function getUser() {
return (
<Query query={query.USER_INFO}>
{({ loading, error, data }) => {
if (loading) return <div>Loading</div>;
if (error) return <div>error</div>;
const userInfo = {
name: data.user.name,
age: data.user.age,
}
}}
</Query>
);
}
//need userInfo object to go here
export const UserContext = React.createContext(userInfo);
How can I get the return of the Query to then use in React.createContext? The reason I want to do it this way is to avoid rewriting this Query in every component where I want info of the user who has logged in.
To be honest, to me it seems like you're just missing a return statement:
function getUser() {
return (
{({ loading, error, data }) => {
if (loading) return Loading;
if (error) return error;
const userInfo = {
name: data.user.name,
age: data.user.age,
}
return userInfo // ----- this here
}}
</Query>
);
}
//need userInfo object to go here
export const UserContext = React.createContext(userInfo);
but i didn't test it out and haven't taken this approach - give it a go see, if it helps

Apollo React Nested Query & Mutation

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']
})
}}

Resources