How to force update data cache in react-apollo? - reactjs

How to refetch fresh data when you revisit a page whose data is powered by react-apollo?
Say, I visit a listing page for the first time. apollo will fetch the query and caches it by default. So, when you visit the same page again during the session, it will populate the data from its cache store. How to force apollo to refetch data every time when the component mounts?

You can use apollo's fetchPolicy. Based on this, it will decide to execute the query or not again.
Example:
const graphQLOptions = {
name: 'g_schemas',
options: (props) => {
return {
variables: {
name: props.name,
},
fetchPolicy: 'cache-and-network',
}
},
}
Hope it helps.

Adding to Pranesh's answer: the fetchPolicy you're looking for is network-only.

In case you are using react-apollo's Query component, for example:
import { Query } from "react-apollo";
You can apply the fetchPolicy through its props. See below an example:
import gql from 'graphql-tag';
import React from 'react';
import { Query } from 'react-apollo';
const CounterView = ({ counter }) => (
<div>{counter}</div>
);
const GET_COUNTER = gql`
{
counter
}
`;
const Counter = () => (
<Query query={GET_COUNTER} fetchPolicy={'network-only'}>
{({ data }) => {
return <CounterView {...data} />;
}}
</Query>
);
export default Counter;
References:
https://www.apollographql.com/docs/react/essentials/queries.html#basic
https://www.apollographql.com/docs/react/essentials/queries.html#props
https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-config-options-fetchPolicy

Related

history.push sometimes triggers refresh sometimes not

Using React 17 and React Router Dom 5.2 I've encountered a strange phenomenon.
In a simply CRUD application, there is a component for listing up all items, one for creating, and one for editing.
A little bit simplified, the code looks like this:
parent:
<Switch>
<Route path="/item/create" component={ItemCreatePage}></Route>
<Route path="/item/:itemId" component={ItemEditPage}></Route>
<Route path="/item" component={ItemList}></Route>
</Switch>
create:
import React from "react"
import { useHistory } from "react-router-dom"
import { ItemCreateOrEditComponent } from "./item-create-and-edit"
import { CreateItemInput, useCreateItemMutation } from "/types"
export const ItemCreatePage: React.FC = () => {
const history = useHistory()
const [createItemMutation] = useCreateItemMutation()
async function createItem(itemToSave: CreateItemInput) {
await createItemMutation({ variables: { data: itemToSave } })
history.push(`/item`)
}
return <ItemCreateOrEditComponent createOrEdit="create" saveFunction={createItem}></ItemCreateOrEditComponent>
}
edit:
import React, { useState } from "react"
import { RouteComponentProps, useHistory } from "react-router-dom"
import { ItemCreateOrEditComponent } from "../../../components/form/item-create-and-edit"
import { UpdateItemInput, useItemQuery, useUpdateItemMutation } from "/types"
type Props = RouteComponentProps<{ itemId: string }>
export const ItemEditPage: React.FC<Props> = (props) => {
const itemId = props.match.params.itemId
const [item, setItem] = useState<UpdateItemInput | undefined>(undefined)
const { data } = useItemQuery({ variables: { id: itemId } })
const history = useHistory()
const [updateItemMutation] = useUpdateItemMutation()
React.useEffect(() => {
if (data?.item) {
setItem(data.item)
}
}, [data])
async function updateItem(itemToSave: UpdateItemInput) {
await updateItemMutation({ variables: { data: itemToSave, }, })
history.push(`/item`)
}
return <ItemCreateOrEditComponent inputItem={item} createOrEdit="edit" saveFunction={updateItem}></ItemCreateOrEditComponent>
}
So, create and edit do basically the same things: Use ItemCreateOrEditComponent, trigger a DB function for saving (Apollo GraphQL in this case, but doesn't matter) and then go back to the list: history.push("/item").
However, the strange thing is: After editing, the list is refreshed with the updated item. After creating, the list is not updated.
It's not a race condition, adding a few seconds of timeout before history.push("/item") doesn't change anything.
The only differences between creating and editing I see are
For editing, the data gets fetched from the back-end
For editing, there is a URL prop containing the items id
I cannot image why these two things should have any impact on whether a refresh is triggered or not.
Edit:
ItemCreateOrEditComponent provides a form, does some validation and transformation, and then finally calls this.props.saveFunction(this.state.item) in the exact same way both for edit and create. And till it comes to history.push I cannot see any difference in behavior there.

Call a GraphQL Mutation once after React component mounts

After a user creates a profile, they receive a link in their email that sends them back to the site with a verifyToken in the url. If the token matches the token that is stores in the database, their isVerified status is stored in the database with the value true.
new-profile.js
import VerifyEMail from '../components/VerifyEmail';
const NewProfilePage = props => (
<div>
<VerifyEMail verifyToken={props.query.verifyToken} />
</div>
);
export default NewProfilePage;
Currently, I have this implemented and working using a form with a "Verify" button that the user must click to call the graphQL mutation, verifyEmail. Since this sets the isVerified value to true in the database, I know that everything is working as it should.
../components/VerifyEmail.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Mutation } from 'react-apollo';
import gql from 'graphql-tag';
const VERIFY_EMAIL_MUTATION = gql`
mutation VERIFY_EMAIL_MUTATION($verifyToken: String!) {
verifyEmail(verifyToken: $verifyToken) {
isVerified
}
}
`;
class VerifyEmail extends Component {
render() {
const { verifyToken } = this.props;
return (
<Mutation mutation={VERIFY_EMAIL_MUTATION} variables={{ verifyToken }}>
{verifyEmail => (
<form
onSubmit={async () => {
await verifyEmail(verifyToken);
}}
>
<button type="submit">Verify</button>
</form>
)}
</Mutation>
);
}
}
VerifyEmail.propTypes = {
verifyToken: PropTypes.string.isRequired,
};
export default VerifyEmail;
However, I really don't want to force my users to have to click a button to fire the mutation. I would like it to be called once the component loads. I have been racking my brain for a day and a half on this, tried so many things, and just can't seem to find anything that works.
I've seen some solutions using React hooks, Apollo hooks, componentDidMount, etc. My mind is just having a difficult time seeing it any more. This link had some of the best solutions that I found so far, but I couldn't figure out how to implement them...
[Feature idea] Execute a mutation on mount #1939
Any help to point me in the right direction would be really appreciated. Thank you.
This is far simpler application when using React hooks:
import React, { useEffect } from "react";
function VerifyEmail({ verifyToken }) {
const [ verifyEmail, { loading, data, error }] = useMutation(VERIFY_EMAIL_MUTATION);
useEffect(() => {
verifyEmail({
variables: { verifyToken },
});
}, []);
return (
<>
{loading && <p>Loading...</p>}
{data && <p>Verified successfully!</p>}
{error && <p>Error!</p>}
</>
)
}
If you somehow want to keep using classes, the only solution is to create a component and utilise componentDidMount of the component for this purpose.
// Current component:
<Mutation mutation={VERIFY_EMAIL_MUTATION} variables={{ verifyToken }}>
{verifyEmail => (
<SendEmail token={verifyToken} verify={verifyEmail} />
)}
</Mutation>
// Send Email component
class SendEmail extends Component {
componentDidMount() {
const { token, verify } = this.props;
verify(token);
}
render() {
return (
//handle loading, data and error states
)
}
}

Firing React Apollo mutation on page load

I am trying to implement email verification system in react-apollo application and running into an issue. The problem is that I want to fire a GraphQL mutation on page load when a user visits a link with a verification token. The mutation currently is fired on a button click, but I want it to happen on page load.
I tried to return the mutation from render but it sent the application into an infinite loop.
return (
<Mutation
mutation={VERIFY_EMAIL_MUTATION}
variables={{ id }}
onCompleted={() => this.setState({ userVerified: true })}
>
{(verifyEmail, { loading, error }) => {
verifyEmail();
}
</Mutation>
How can I implement firing this mutation on page load?
Use compose and pass it down as a function to your component. The following method allows you to pass multiple mutations/queries, you can use them where-ever you want without and with triggers.
import React from "react";
import { compose, graphql } from "react-apollo";
import {
VERIFY_EMAIL_MUTATION
} from "../GraphqlQueries/ServerQueries";
class YourComponent extends React.Component {
componentWillMount() {
this.props.verifyEmail({variables: {email: "your_email", variable2: "variable2" }});
}
render() {
return (
<React.Fragment>
Your Render
</React.Fragment>
);
}
}
export default compose(
graphql(VERIFY_EMAIL_MUTATION, {
name: "verifyEmail"
})
)(YourComponent);
Rather than compose you can do it like this
useEffect(() => {
MutationName({
variables: {
variable1: variable1Value
}
});
}, []);
useEffect behave as on pageload.

How to update GraphQL query on button click

I'm looking for a good way to update the "orderBy: createdAt_ASC" portion of the below graphql query when one of the two buttons below are clicked.
The default order is createAt_ASC and want the user to be able to switch between them.
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY {
items(orderBy: createdAt_ASC) {
id
title
description
image
}
}
`;
Buttons:
<button onClick={sortNew}>Newest</button>
<button onClick={sortOld}>Oldest</button>
First of all, change your query
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY($orderBy: String!) {
items(orderBy: $orderBy) {
id
title
description
image
}
}
`;
On react component use react-apollo
import { compose, graphql } from 'react-apollo'
class ReactComponentName extends Component {
......
//Inside render return this buttons
<button onClick={this.changeOrder.bind(this,"sortNew")}>Newest</button>
<button onClick={this.changeOrder.bind(this,"sortOld")}>Oldest</button>
.......
// And change your export statement and using HOC
export default compose(
graphql(ALL_ITEMS_QUERY, { name: 'allItemQuery' }),
// import query and bind this into props using compose
)(ReactComponentName)
onClick call a function
async changeOrder(order) {
const { allItemQuery } = this.props
const result = await allItemQuery({ variables: { 'orderBy': order } })
//Set state or store filter data
}
Change your state variable or render result data.
Or
Do this cool Query component in Apollo client

Is there a way to pass a dynamic GraphQL query to a graphql() decorated Component when using Apollo Client 2.0?

I encounter this issue sometimes when I am working with dynamic data. It's an issue with higher-order components which are mounted before the data they need is available.
I am looking to decorate a component with the graphql() HOC in Apollo Client, like this:
export default compose(
connect(),
graphql(QUERY_NAME), <-- I want QUERY_NAME to be determined at run-time
)(List)
The problem is I don't know how to get Apollo to use a query that is determined by the wrapped component at run-time.
I have a file that exports queries based on type:
import listFoo from './foo'
import listBar from './bar'
import listBaz from './baz'
export default {
foo,
bar,
baz,
}
I can access them by listQueries[type], but type is only known inside the component, and it is available as this.props.fromRouter.type.
Is there a strategy I can use to achieve:
export default compose(
connect(),
graphql(listQueries[type]),
)(List)
I think there might be a way to do it like this:
export default compose(
connect(),
graphql((props) => ({
query: listQueries[props.fromRouter.type],
})),
)(List)
Am I on the right track?
Another possible solution could be to make the Component generate its own sub-component that is wrapped with graphql() because the query would be known then.
For example:
const tableWithQuery = graphql(listQueries[props.fromRouter.type])((props) => {
return <Table list={props.data} />
})
I think I figured it out.
I have a Router Component that reads this.props.match.params to get the type of view and requested action.
With this information, I can create just one List, Create, Edit, and View Component and supply each with whatever queries are needed.
I created a function that gets all queries and mutations for a supplied type.
It was actually quite simple to just take the component such as <List /> and wrap it with graphql() and give it the correct query or mutation that was just determined.
Now, the components mount with this.props.data being populated with the correct data
I spread in all the queries and mutations just in case I need them. I suspect I will need them when I go to read this.props.data[listQueryName]. (which will grab the data in, for example, this.props.data.getAllPeople)
Here is the logic (I will include all of it, to minimize confusion of future searchers):
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose, graphql, withApollo } from 'react-apollo'
import listQueries from './list/queries'
import createMutations from './forms/create/mutations'
import editMutations from './forms/edit/mutations'
import viewQueries from './forms/view/queries'
import List from './list/List'
import Create from './forms/create/Create'
import Edit from './forms/edit/Edit'
import View from './forms/view/View'
// import Delete from './delete/Delete'
class Router extends Component {
constructor(props) {
super(props)
this.state = {
serverErrors: [],
}
}
getGraphQL = (type) => {
console.log('LIST QUERY', listQueries[type])
console.log('LIST QUERY NAME', listQueries[type].definitions[0].name.value)
console.log('CREATE MUTATION', createMutations[type])
console.log('CREATE MUTATION NAME', createMutations[type].definitions[0].name.value)
console.log('EDIT MUTATION', editMutations[type])
console.log('EDIT MUTATION NAME', editMutations[type].definitions[0].name.value)
console.log('VIEW QUERY', viewQueries[type])
console.log('VIEW QUERY NAME', viewQueries[type].definitions[0].name.value)
return {
listQuery: listQueries[type],
listQueryName: listQueries[type].definitions[0].name.value,
createMutation: createMutations[type],
createMutationName: createMutations[type].definitions[0].name.value,
editMutation: editMutations[type],
editMutationName: editMutations[type].definitions[0].name.value,
viewQuery: viewQueries[type],
viewQueryName: viewQueries[type].definitions[0].name.value,
}
}
renderComponentForAction = (params) => {
const { type, action } = params
const GQL = this.getGraphQL(type)
const {
listQuery, createMutation, editMutation, viewQuery,
} = GQL
// ADD QUERIES BASED ON URL
const ListWithGraphQL = graphql(listQuery)(List)
const CreateWithGraphQL = graphql(createMutation)(Create)
const EditWithGraphQL = compose(
graphql(viewQuery),
graphql(editMutation),
)(Edit)
const ViewWithGraphQL = graphql(viewQuery)(View)
if (!action) {
console.log('DEBUG: No action in URL, defaulting to ListView.')
return <ListWithGraphQL fromRouter={params} {...GQL} />
}
const componentFor = {
list: <ListWithGraphQL fromRouter={params} {...GQL} />,
create: <CreateWithGraphQL fromRouter={params} {...GQL} />,
edit: <EditWithGraphQL fromRouter={params} {...GQL} />,
view: <ViewWithGraphQL fromRouter={params} {...GQL} />,
// delete: <Delete fromRouter={params} {...GQL} />,
}
if (!componentFor[action]) {
console.log('DEBUG: No component found, defaulting to ListView.')
return <ListWithGraphQL fromRouter={params} {...GQL} />
}
return componentFor[action]
}
render() {
return this.renderComponentForAction(this.props.match.params)
}
}
Router.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({ type: PropTypes.string }),
}).isRequired,
}
export default compose(connect())(withApollo(Router))
If this code becomes useful for someone later. I recommend commenting everything out except code necessary to render the List View. Start with verifying the props are coming in to a little "hello world" view. Then, you will be done the hard part once you get correct data in there.

Resources