Graphql urql refetch every n seconds - reactjs

Im using Typescript, React- and graphql with the urql client library.
My Query looks something like this:
query objectId($id: Int!) {
object(id: $id) {
id
name
__typename
}
}
This is how I call the query:
const [{ data }] = useObjectIdQuery({ variables: { id }, pause: !id });
Question:
How can i refetch every n seconds without reloading the page?
My backend reads JSON files and they update consistently.
Now I have looked into the documentation here, and also on a bunch of Stackoverflow and no-name sites.
Thank you.

I found out that the documentation provides a function for that. I built myself a hook to use it in the whole project for any query. Just becareful with the query parameter, it has to be the already build graphql DocumentNode as parameter.
You can import them like this for each Query:
import { ObjecIdDocument } from "../../graphql";
The graphql path may be different in your case.
This is the full Hook:
import { useEffect } from "react";
import { useQuery } from "urql";
import { DocumentNode } from "graphql";
const useRefreshingQuery = (variables: object, query: DocumentNode, delayInSec: number, pause: boolean) => {
const [result, reexecuteQuery] = useQuery({
query: query,
variables: variables,
pause: pause,
});
useEffect(() => {
if (result.fetching) {
return;
}
const timerId = setTimeout(() => {
reexecuteQuery({ requestPolicy: "network-only" });
}, delayInSec * 1000);
return () => clearTimeout(timerId);
}, [result.fetching, reexecuteQuery, variables]);
return result;
};
export default useRefreshingQuery;
You can use the Hook like this:
import { ObjecIdDocument } from "../../graphql";
const result = useRefreshingQuery({ id: UID }, ObjectIdDocument, 10, !UID);

Related

How do I invalidate queries and fetch latest data in TRPC?

I am simply trying to get latest data from the server after performing a mutation. My code looks something like this:
const utils = trpc.useContext()
const markAsUnreadMutation = trpc.useMutation(['update-mark-as-unread'], {
onSuccess() {
utils.invalidateQueries() //THIS IS NOT WORKING!
},
onError(data) {
toast({
type: 'error',
message: data.message,
})
},
})
function markAsUnread(isUnread: boolean) {
markAsUnreadMutation.mutate({
id: parseInt(channel.id),
markAsUnread: isUnread,
})
}
Invalidating a single query
You can invalidate a query relating to a single procedure and even filter based on the input passed to it to prevent unnecessary calls to the back end.
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useContext();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}
If you are using TRPC v10, you can do something like this:
utils.your.route.invalidate()

ReactJS : import GraphQL queries dynamically based on a State

I query a specific variable called nameTranslated from my schema, It takes the parameter of the locale with is En-CA, Fr-FA etc and gets the desired word in french. And the way I handle this in my frontend reactjs application is like this:
export const App = () => {
const { locale } = useIntl()
const LOAD_TABLE = gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${l}")
defaultClass {
nameTranslate(language:"${l}")
}
}
}
`
useEffect(() => {
// a function to fetch LOAD_TABLE
},[locale])
}
The above code works perfectly fine and whenever I change the locale variable it re fetches the query. But the problem with this is i have many other query I need to work with, my file length becomes too long and hard to manage. At the same time if I pull the file out, I lose the privilage of dynamacally adding a type for nameTranslate.. How can I solve this issue?
You can make it more modular but still dynamic by using custom hooks, for example:
// hooks/useItemsQuery.js
function useItemsQuery(locale) {
const itemsQuery = useMemo(() => gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`, [locale])
return itemsQuery
}
// App.js
export const App = () => {
const { locale } = useIntl()
const itemsQuery = useItemsQuery(locale)
useEffect(() => {
// a function to fetch itemsQuery
}, [itemsQuery])
}
Or if you need to call outside of React just a normal function will do. I think this won't have a perf impact as es6 tpl literals are cached (the gql`` part) as long as the variables don't change, even inside a function. If that's the case the use of useMemo above is redundant anyway.
function itemsQuery(locale) {
return gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`
}

InMemoryCache from #apollo/client gives warning

I have started getting this warning, when I update an "Transaction" with a mutation.
The code is old and have newer showed this warning before. I do not know when this regression started.
Oddly enough, In my mind there should be no cache either, since fetchPolicy: "network-only" is set.
How can I get rid of the warning?
invariant.esm.js:42 Cache data may be lost when replacing the transactions field of a Query object.
To address this problem (which is not a bug in Apollo Client),
define a custom merge function for the Query.transactions field,
so InMemoryCache can safely merge these objects:
existing: [{"__ref":"Transaction:5feabda25e7967001267ffd2"},
{"__ref":"Transaction:5feabda55e7967001267ffd3"},
{"__ref":"Transaction:5feabda75e7967001267ffd4"},
{"__ref":"Transaction:5feabda95e7967001267ffd5"},
{"__ref":"Transaction:5feabdab5e7967001267ffd6"},
{"__ref":"Transaction:60127209c3731400116fe0c5"},
{"__ref":"Transaction:602543cde12cd00011881a8b"},
{"__ref":"Transaction:602544e7e12cd00011881a91"},
{"__ref":"Transaction:602f7d9be14be20011a5cbec"},
{"__ref":"Transaction:6033b4d5ad34870011e7ba08"},
{"__ref":"Transaction:603494fbad34870011e7bf07"},
{"__ref":"Transaction:6038a0519d844c00116e544f"},
{"__ref":"Transaction:6038a05f9d844c00116e545e"},
{"__ref":"Transaction:6038a06c9d844c00116e549e"},
{"__ref":"Transaction:6038a06f9d844c00116e54a1"},
{"__ref":"Transaction:6038a0549d844c00116e5452"},
{"__ref":"Transaction:6038a0629d844c00116e545f"},
{"__ref":"Transaction:6038a0699d844c00116e549a"},
{"__ref":"Transaction:603cd5c39d844c00116f232b"},
{"__ref":"Transaction:603ce07d9d844c00116f2342"},
{"__ref":"Transacti
incoming: [{"__ref":"Transaction:5feabda25e7967001267ffd2"},
{"__ref":"Transaction:5feabda55e7967001267ffd3"},
{"__ref":"Transaction:5feabda75e7967001267ffd4"},
{"__ref":"Transaction:5feabda95e7967001267ffd5"},
{"__ref":"Transaction:5feabdab5e7967001267ffd6"},
{"__ref":"Transaction:60127209c3731400116fe0c5"},
{"__ref":"Transaction:602543cde12cd00011881a8b"},
{"__ref":"Transaction:602544e7e12cd00011881a91"},
{"__ref":"Transaction:602f7d9be14be20011a5cbec"},
{"__ref":"Transaction:6033b4d5ad34870011e7ba08"},
{"__ref":"Transaction:603494fbad34870011e7bf07"},
{"__ref":"Transaction:6038a0519d844c00116e544f"},
{"__ref":"Transaction:6038a05f9d844c00116e545e"},
{"__ref":"Transaction:6038a06c9d844c00116e549e"},
{"__ref":"Transaction:6038a06f9d844c00116e54a1"},
{"__ref":"Transaction:6038a0549d844c00116e5452"},
{"__ref":"Transaction:6038a0629d844c00116e545f"},
{"__ref":"Transaction:6038a0699d844c00116e549a"},
{"__ref":"Transaction:603cd5c39d844c00116f232b"},
{"__ref":"Transaction:603ce07d9d844c00116f2342"},
{"__ref":"Transacti
For more information about these options, please refer to the documentation:
* Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers
* Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects
From code that looks like this:
const UPDATE_TRANSACTION = gql`
mutation updateTransaction($input: UpdateTransactionInput!) {
updateTransaction(input: $input) {
_id
status
}
}
`;
export const DealBlotterGrid = ({ startDate }: DealBlotterGridPropsType): ReactElement => {
const swedenIsoString = moment.tz(startDate, "Europe/Stockholm").format();
const { loading, error, data } = useQuery(GET_TRANSACTIONS, {
variables: { tradeTimestampStart: swedenIsoString },
fetchPolicy: "network-only",
pollInterval: 10000
});
const [updateTransactionStatus] = useMutation(UPDATE_TRANSACTION, {
refetchQueries: [
{
query: GET_TRANSACTIONS,
variables: { tradeTimestampStart: swedenIsoString }
}
]
});
...

React Context and useCallback API refresh in child component best practice

I am using the Context API to load categories from an API. This data is needed in many components, so it's suitable to use context for this task.
The categories can be expanded in one of the child components, by using a form. I would like to be able to tell useCategoryLoader to reload once a new category gets submitted by one of the child components. What is the best practice in this scenario? I couldn't really find anything on google with the weird setup that I have.
I tried to use a state in CategoryStore, that holds a boolean refresh State which gets passed as Prop to the callback and can be modified by the child components. But this resulted in a ton of requests.
This is my custom hook useCategoryLoader.ts to load the data:
import { useCallback } from 'react'
import useAsyncLoader from '../useAsyncLoader'
import { Category } from '../types'
interface Props {
date: string
}
interface Response {
error?: Error
loading?: boolean
categories?: Array<Category>
}
const useCategoryLoader = (date : Props): Response => {
const { data: categories, error, loading } = useAsyncLoader(
// #ts-ignore
useCallback(() => {
return *APICALL with modified date*.then(data => data)
}, [date])
)
return {
error,
loading,
categories
}
}
export default useCategoryLoader
As you can see I am using useCallback to modify the API call when input changes. useAsyncloaderis basically a useEffect API call.
Now this is categoryContext.tsx:
import React, { createContext, FC } from 'react'
import { useCategoryLoader } from '../api'
import { Category } from '../types'
// ================================================================================================
const defaultCategories: Array<Category> = []
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories
})
// ================================================================================================
const CategoryStore: FC = ({ children }) => {
const { loading, categories } = useCategoryLoader({date})
return (
<CategoryContext.Provider
value={{
loading,
topics
}}
>
{children}
</CategoryContext.Provider>
)
}
export default CategoryStore
I'm not sure where the variable date comes from in CategoryStore. I'm assuming that this is an incomplete attempt to force refreshes based on a timestamp? So let's complete it.
We'll add a reload property to the context.
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories,
reload: () => {},
})
We'll add a state which stores a date timestamp to the CategoryStore and create a reload function which sets the date to the current timestamp, which should cause the loader to refresh its data.
const CategoryStore: FC = ({ children }) => {
const [date, setDate] = useState(Date.now().toString());
const { loading = true, categories = [] } = useCategoryLoader({ date });
const reload = useCallback(() => setDate(Date.now.toString()), []);
return (
<CategoryContext.Provider
value={{
loading,
categories,
reload
}}
>
{children}
</CategoryContext.Provider>
)
}
I think that should work. The part that I am most iffy about is how to properly memoize a function that depends on Date.now().

GraphQL Return Query Result as an Array

I have been following the React-Apollo and Node.js GraphQL Tutorials on https://www.howtographql.com/ and was able to get a demo up and running.
What I would like to do now is return the result of my query as an array.
My schema.graphql file looks like this:
type Query{
pullTickets (where: TicketWhereInput): [Ticket!]!
}
And the resolver looks like:
const resolvers = {
Query: {
pullTickets: (root, args, context, info,) => {
return context.db.query.tickets({}, info)
}
}
}
What I have tried so far is:
import React, { Component } from 'react';
import Query from "react-apollo/Query";
import gql from "graphql-tag";
const GET_TICKETS = gql`
query getTickets($startDate: DateTime!, $endDate: DateTime!) {
pullTickets (where: {
AND:[{DateCloseDate_gt: $startDate}, {DateCloseDate_lt: $endDate}]})
{
id
}
}
`;
export default function GetTickets ( startDate, endDate ) {
var temp = [];
<Query query={GET_TICKETS} variables={{ startDate, endDate }} >
{({ loading, error, data }) => {
if (loading) return 'Loading...';
if (error) return `Error!: ${error}`;
// This doesn't seem to do anything
temp = data.pullTickets
}}
</Query>
// temp is showing up as "undefined" in the console
console.log(temp);
return temp
}
I should be getting a list of Tickets but I am getting undefined.
How to use client.query or client.mutation without using jsx
I had the same issue, I wanted to use query without using any jsx, the solution is to use ApolloClient, at some point in your app you will use ApolloProvider which u will need to give it an instance of ApolloClient as a prop, all u have to do is export that client and u can then use it anywhere else not only in the ApolloProvider.
Here is an example
initApollo.js
this is where Apollo client is initialized.
import { ApolloClient } from 'apollo-client';
.
.
.
initApolloClient ({ uri }) => {
.
.
.
return new ApolloClient({
link,
cache,
});
}
export default initApolloClient
useClientWithoutJsx.js
import initApollo from './initApollo';
client = initApollo({ uri: 'http://localhost:4000' });
client.query(({query: SOME_QUERY, variables: {id: 2}) => {
// return data
})
does this answers your question ?

Resources