Apollo Refetch Query Component with new Variables - reactjs

So I have this Apollo Query Component like this:
<Query
fetchPolicy='network-only' // also tried without and with 'no-cache'
query={GET_MENUS}
variables={{
foo // This has the default value of the state
}}
>
{({ loading, error, data, refetch }) => {
// Display Data here
// We have an Imput here that can change the State of Bar in the parent Component
<Button
onPress={() => {
/*refetch({
foo: { bar}
}); */
setBar(blubb); // I am using react hooks (useState)
}}
text='Refresh!'
/>
}
)}
</Query>
I tried to refetch by using the refetch method and also by just updating the state. Actually I checked the Apollo Server and in both methods the new variables get passed, but the new Data is not updated. The funny thing is, that if I just use another default value in the state, it works fine. I also tried different fetch-policies without any luck.
I thought it should be quite basic, but I didn't find any solution so far...
So how do I get data with my new variables?
EDIT:
GET_MENUS is a bit complicated, but this is the whole thing. I am passing the variables into different resolvers, because they are nested. The Foo Bar thingy is the "daily" variable
const GET_MENUS = gql`
query getMenus($lat: Float!, $lng: Float!, $daily: Daily) {
getMenus(lat: $lat, lng: $lng) {
distance
location {
_id
street
streetNumber
plz
city
coordinates
shopIDs {
name
togo
shopType
menus(daily: $daily) {
_id
name
price
hot
sweet
togo
allergies
components
}
}
}
}
}
`;

My solution to refetch using variables in Apollo 3.0:
import { gql, useApolloClient } from "#apollo/client";
const client = useApolloClient();
const SEARCH_PROJECTS = gql``
await client.query({
query: SEARCH_PROJECTS,
variables: { page: 1, limit: 1 },
notifyOnNetworkStatusChange: true,
fetchPolicy: "network-only"
})
See more about the fetch policy here and here.
My context was the following: I fetch a list of projects, then the user can remove or update the projects. The list of projects, the project, the update and delete are different components. The default refresh provided by Apollo doesn't allow me to send the variables for the project' pagination, so when I remove or update a project I refresh it manually, without the need to create a structure where I can use the refresh or fetch more option from the component "list of projects"

Related

Apollo Client React and local state best practices

I am working on my first GraphQL React Client app and I am not sure about some best practices.
I have a query, that gets me places from an API to be shown on a map.
In the map.js component I fetch the data with this query:
const GET_PLACES_ON_MAP = gql`
query Region($input: MapInput!) {
map(input: $input) {
places {
id
name
distance
long
lat
}
infos {
totalPlacesInArea
responseTime
}
}
}
`;
const { loading, error, data } = useQuery(GET_PLACES_ON_MAP, someQueryParams);
The response includes infos about the total number of places and the response time of API response.
This infos object should be used in other components too (e.g. in some kind of dashboard) and it changes everytime a new query is made in the map component.
My question no 1:
What's the best practice to use infos in another component that do not query the API itselves?
My first try was to save infos in a local state graphql object like with the following mutation and resolver code (this sets the infos in local state but I get endless rendering of the map component):
const SET_API_INFO = gql`
mutation {
setApiInfo(params: $params) #client
}
`;
const GET_API_INFO = gql`
{
apiInfo #client {
totalPlacesInArea
responseTime
}
}
`;
const resolvers = {
Mutation: {
setApiInfo: (_root, variables, { cache, getCacheKey }) => {
const { apiInfo } = cache.readQuery({ query: GET_API_INFO });
const { params } = variables;
_.map(params, (value, key) => {
apiInfo[key] = value;
});
cache.writeData({ data: { apiInfo } });
},
},
};
My question no 2:
I was looking for some tutorial or an open source project that uses Apollo Client & React to learn more about the architecture of a really complex app. Is there anywhere a good example that goes further then query, mutation, subscription?
You don't need to store your data in the client cache because Apollo supports fetchPolicy. When you use the same query with the same variables, query fields in any component, Apollo will look up its cache query first before making a new network request. If the query has existed, Apollo will return the last query result.
By default, Apollo Client's fetch policy is cache-first, which means it checks the cache to see if the result is there before making a network request. Since we want this list to always reflect the newest data from our graph API, we set the fetchPolicy for this query to network-only.
I don't know any Apollo complex app, but you can read more about Apollo's best practices in Apollo blog.

How to get refetchQueries data in reactjs 16.8

I'm trying to use refetchQueries to get an updated list of record after a mutation. I have tried below code to get updated data but the updated data showing in network tab of the developer console.
This is for reactjs 16.8 and "react-apollo": "^2.5.5" and it seems awaitRefetchQueries: true not working
client.mutate({
mutation: ADD_ACTIVE_CREW,
variables: {
activeCrewInfo: {
employee: activeCrew[0].vp_id,
date_on_crew: new Date(),
crew_id: selectedCrewId
}
},
options: () => ({
refetchQueries: [
{
query: GET_EMPLOYEE_BY_CREW_ID_QUERY,
variables: { crew_id: crew_id }
}
]
}),
awaitRefetchQueries: true
});
I'm getting correct response in developer console's network tab, but not able to access in my reactjs application.
The mutation data itself will never reflect the refetched queries.
Any queries you tell Apollo to refetch will be requested and then have their result written to the cache. That means any Query components or components using useQuery or watchQuery will be updated, provided the query and variables parameters match what's in refetchQueries.
If you don't already have a component that uses that query, you need to add one.

RefetchQueries does not correctly update the store

Intended outcome:
I am trying to refetch a query after a mutation. This works perfectly everywhere but I have a problem with this particular query.
const alertsSettingsQuery = gql`
query alertsSettingsQuery($issuerRoleNames: [String], $receiverRoleNames: [String]) {
alertsSettings(issuerRoleNames: $issuerRoleNames, receiverRoleNames: $receiverRoleNames) {
...
}
}
`;
Component querying the initial datas without variables :
export default compose(
graphql(alertsSettingsQuery, {name: "alertsSettingsData"}),
...,
)(Administration);
Component refetching the datas without variables :
mutate({
variables: updatedAlert,
refetchQueries: () => [{query: alertsSettingsQuery}]
}).then((data) => {
...
}).catch(err => {
...
});
Actual outcome:
If I check the network tab, the datas are correctly refetched and updated but the component doesn't receive the updated datas.
If I use the apollo devtools to see what's in my cache, I have this for the original datas :
And this from the refetch :
It seems like the two datas are not identified the same way.
If I change my refetchQueries to this :
refetchQueries: () => [{query: alertsSettingsQuery, variables: {issuerRoleNames: null, receiverRoleNames: null}}]
It works as expected.
I don't understand why I need to explicitly set the variables to null here, since I don't do that for any other refetch.
Version
apollo-client#2.3.4
react-apollo#<2.1.5>

Handle URL Parameters with Gatsby

I'm using React & Gatsby to create a website and I've got everything laid out the way I want, I'm just having some problems understanding how to use URL Route parameters to change content that is displayed.
For example, say I have a page categories.js which listens to http://website.com/categories but I want to be able to dynamically handle any URL parameters like such:
http://website.com/categories/animals
When using gatsby-link like so: <Link to="/categories/animals" /> it wants me to create a file called animals.js in a categories folder. Instead, I want categories.js to be able to handle the rendering for this page and select the content appropriate based on the category passed in the URL parameters.
Other than the items displayed, this page is exactly the same through all categories, so it doesn't make sense for every category to have it's own static page.
I think you are wrong when you say:
Other than the items displayed, this page is exactly the same through all categories, so it doesn't make sense for every category to have it's own static page.
In fact, that's exactly where I find GatsbyJS so useful, as it is a static site generator.
It means that you can give Gatsby a template component that will have the same layout for all of your categories, and then Gatsby will fetch data and create static pages for you at build time.
Gatsby is not limited to making pages from files like many static site generators. Gatsby lets you use GraphQL to query your data and map the data to pages—all at build time. (from Gatsby official tutorial)
The idea would be something like this:
in /gatsby-node.js
const path = require(`path`); // you will need it later to point at your template component
exports.createPages = ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators;
// we use a Promise to make sure the data are loaded
// before attempting to create the pages with them
return new Promise((resolve, reject) => {
// fetch your data here, generally with graphQL.
// for example, let say you use your data from Contentful using its associated source plugin
graphql(`
{
allContentfulCategories {
edges {
node {
id
name
description
# etc...
}
}
}
}
`).then(result => {
// first check if there is no errors
if (result.errors) {
// reject Promise if error
reject(result.errors);
}
// if no errors, you can map into the data and create your static pages
result.data.allContentfulCategories.edges.forEach(({ node }) => {
// create page according to the fetched data
createPage({
path: `/categories/${node.name}`, // your url -> /categories/animals
component: path.resolve('./src/templates/categories.js'), // your template component
context: {
// optional,
// data here will be passed as props to the component `this.props.pathContext`,
// as well as to the graphql query as graphql arguments.
id: node.id,
},
});
});
resolve();
});
});
};
And then you can simply fetch the data on your template component
in /src/templates/categories.js
import React from "react";
export default ({ data: { contentfulCategories: category } }) => {
return (
<div>
<h1>{category.name}</h1>
<div>{category.description}</div>
</div>
);
};
// we can query the needed category according to the id passed in the
// context property of createPage() in gatsby-node.js
export const query = graphql`
query CategoryQuery($id: String!) {
contentfulCategories(id: { eq: $id }) {
name
description
}
}
`;
If you insist in dynamically rendering your categories pages, you can take example of this question that is sort of similar to yours, but note that you will have to manually handle re-rendering if the page's props changes in the componentWillReceiveProps lifecycle method of the React class component.
But I really don't think this is a reliable solution.
EDIT:
I've found a better answer after searching forever! -
https://github.com/gatsbyjs/gatsby/issues/13965#issuecomment-617364363
the example for your case I imagine would be something like this:
gatsby-node.js:
createPage({
path: `/categories/:id`,
matchPath: `/categories/:id`,
component: path.resolve(`./src/pages/categories.js`),
})
categories.js:
import React from "react"
export default function Booking({ id }) {
return <div>categories #{id}</div>
}
OLD ANSWER:
I seem to have come across exactly the same issue too and couldn't find any answers. My idea was to also use the gatsby-node.js file, but I don't query anything using graphQL.
My version of /src/templates/categories.js:
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
createPage({
path: `/categories/animals`,
component: path.resolve(`./src/pages/categories.js`),
// The context is passed as props to the component as well
// as into the component's GraphQL query.
context: {
id: `animals`, //use this context parameter to do the dynamic stuff in your page
},
})
}
I hope this is useful.

Apollo and React: refreshQueries not working in mutation fired from onClick()

I'm building a simple shopping app. On load, we'll make a query to check whether a cart exists. For now, that query always returns null (I haven't implemented logic yet to check whether a user has a shopping cart already). Then, when a user clicks "create cart", we do a mutation to create it. Finally, we'll use refreshQueries after the mutation finishes to fetch the cart (and its products) by ID, which is returned from the mutation. We then render those products in a pure component.
The problem I'm encountering is that nothing re-renders after users click the button and refreshQueries happens. I know the query is being sent and returning a created cart with products by looking at my developer tools' network tab. It's just that Apollo doesn't seem to notice the change.
Mongo is my back-end.
Here's the relevant code:
// query
import { gql } from 'react-apollo';
export const cartQuery = gql`
query CartQuery($cartId: ID) {
cart(cartId: $cartId) {
_id,
products {
_id,
name
}
}
}
`;
// mutation
import { gql } from 'react-apollo';
export const createCartMutation = gql`
mutation CreateCartMutation {
createCart {
_id
}
}
`;
// Apollo + React stuff
import React from 'react';
import { graphql } from 'react-apollo';
import { createCartMutation } from '../../mutations';
import { cartQuery } from '../../queries';
const BuildCart = ({ mutate }) => (
<button
onClick={() => {
// even when I hardcode the ID of a cart that exists and contains products, it doesn't matter
mutate({
refetchQueries: [{ query: cartQuery, variables: { cartId: '12345abcd' } }],
});
}}
>
Click
</button>
);
const BuildCartConnected = graphql(createCartMutation)(BuildCart);
const Cart = ({ data, data: { cart, loading } }) => {
console.log(cart); // always null, even after clicking button
return (
<div>
<BuildCartConnected />
{loading && <p>Loading...</p>}
// never gets to this step, even though the response is a properly formed cart with an array of products
{cart && cart.products.map(product => (
<p key={Math.random()}>{product.name}</p>
))}
</div>
);
};
const CartConnected = graphql(cartQuery)(Cart);
export default CartConnected;
If it's helpful, here's what the response from refetchQueries looks like in the network tab:
{"data":{"cart":{"_id":"12345abcd","products":[{"_id":"abcdef12345","name":"Product A","__typename":"Product"}],"__typename":"Cart"}}}
Your cart query takes a cartId variable. In the code above, you're not providing that variable, so its value is undefined. Apollo associates that combination of query plus variable(s) with your Cart component.
When you call refetch queries with a different variable than what is provided to the HOC, the results of that new query are fetched and persisted in the store. However, as far as Apollo knows, you still want the results of a query with undefined as the variable value for that component, not this new result you had it fetch.
refetchQueries shouldn't be utilized for what you're trying to do. Instead, the variable for the cart query should be derived from props:
const options = ({cartId}) => ({variables: {cartId}})
const CartConnected = graphql(cartQuery, {options})(Cart)
Then, when you call mutate, it returns a Promise that will resolve to the data returned by the mutation. We can grab the cartId from the response and store it within application state (by calling setState, firing off a Redux action, etc.). You can then pass that state down to your Cart component as a prop. When the state is updated, the prop changes and the query with the newly provided variable is fetched.

Resources