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

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()

Related

Graphql urql refetch every n seconds

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);

How to use useQuery hook without variables?

I'm using Typescript, and I know how to use useQuery hook with variables, but now I have a GraphQL query without variables like below:
const GetTopAlertsQuery = gql`
query getTopAlerts {
getTopAlerts {
ens
walletAddress
}
}
`;
Basically I just need it return all the data in the database without doing any filtering. I have already implemented the back-end and it works successfully, so the query should be good.
I also have set up these two interfaces to hold the data:
interface topAlertValue {
ens: string;
walletAddress: string;
}
interface jsonData {
topalerts: topAlertValue[];
}
And I have tried the below ways, but none of them work:
// attempt #1
const { data } = useQuery<jsonData>(
GetTopAlertsQuery
);
// attempt #2
const data = ({ topalerts }: jsonData ) => {
useQuery(GetTopAlertsQuery);
};
// attempt #3
const data = <Query<Data, Variables> query={GetTopAlertsQuery}>
{({ loading, error, data }) => { ... }}
</Query>
If you know how to use useQuery hook without variables, please help me out! Thanks!

ApolloClient v3 fetchMore with nested query results

I'm using ApolloClient 3 the GitHub GraphQL API to retrieve all releases from a repo.
This is what the query looks like:
query ($owner: String!, $name: String!, $first: Int, $after: String, $before: String) {
repository(owner: $owner, name: $name) {
id
releases(orderBy: {field: CREATED_AT, direction: DESC}, first: $first, after: $after, before: $before) {
nodes {
name
publishedAt
resourcePath
tagName
url
id
isPrerelease
description
descriptionHTML
}
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
}
This is what the result payload looks like:
This returns me the first x entries (nodes). So far, all good.
I need to implement pagination and I make use of the fetchMore function provided by ApolloClient useQuery. Calling fetchMore fetches the next x entries successfully but these are not displayed in my component list.
According to the ApolloClient Pagination documentation, it seems necessary to handle the merging of the fetchMore results with the ApolloClient caching mechanism. The documentation is understandable for simple situations but I am struggling to implement a solution for the situation where the actual array of results that needs to be merged togeher is deeply nested in the query result (repository -> releases -> nodes).
This is my implementation of the InMemoryCache options merge:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
ReleaseConnection: {
fields: {
nodes: {
merge(existing, incoming, options) {
const previous = existing || []
const results = [...previous, ...incoming]
return results
}
}
}
},
}
}
The results array here contains the full list, including the existing entries and the new x entries. This is essentially the correct result. However, my component list which is using the useQuery and fetchMore functionality does not get the new entries after the fetchMore is called.
I have tried various combinations in the inMemoryCacheOptions code above but so far I have been unsuccessful.
To add more context, this is the related component code:
export default function Releases() {
const { loading, error, data, fetchMore } = useQuery(releasesQuery, {
variables: {
owner: "theowner",
name: "myrepo",
first: 15
}
});
if (loading) return null;
if (error) {
console.error(error);
return null;
}
if (data) {
console.log(data?.repository?.releases?.pageInfo?.endCursor);
}
const handleFetchMore = () => {
fetchMore({
variables: {
first: 15,
after: data?.repository?.releases?.pageInfo?.endCursor
}
});
};
return (
<div>
<ul>
{data?.repository?.releases?.nodes?.map(release => (
<li key={release.id}>{release.name}</li>
))}
</ul>
<button onClick={handleFetchMore}>Fetch More</button>
</div>
);
}
After fetchMore the component doesn't rerender with the new data.
If anyone has any other ideas that I could try, I'd be grateful.
I finally managed to solve this. There was no change to the react component code but the InMemoryCacheOptions now looks like this:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
Repository: {
fields: {
releases: {
keyArgs: false,
merge(existing, incoming) {
if (!incoming) return existing;
if (!existing) return incoming;
const { nodes, ...rest } = incoming;
// We only need to merge the nodes array.
// The rest of the fields (pagination) should always be overwritten by incoming
let result = rest;
result.nodes = [...existing.nodes, ...nodes];
return result;
}
}
}
}
}
};
The main change from my original code is that I now define the typePolicy for the releases field of the Repository type. Previously I was trying to get directly to the nodes field of the Release type. Since my Repository type the root of the gql query and used in the component, it now reads the merged results from the cache.
If I specified the typePolicy for Query as mentioned in the docs, I would not be able to specify the merge behaviour for the releases field because it would be one level too deep (i.e. Query -> repository -> releases). This is what lead to my confusion in the beginning.

How to define an action in a mobx store using mobx-react?

Have been following a few tutorials on youtube and have pretty much never seen anyone explicitly define an action that mutates the state they just throw in into the store. I have been doing the same and while it works a 100% it throws a warning on react native. Just wondering how you could define that something is an action and maybe if someone has a good way to separate the actions into a different file. Here is my store.
export function createCurrencyStore() {
return {
currencies: [
'AED',
'ARS',
'AUD',
],
selectedCurrencyFrom: 'USD',
selectedCurrencyTo: 'EUR',
loading: false,
error: null,
exchangeRate: null,
amount: 1,
fromFilterString: '',
fromFilteredCurrencies: [],
toFilterString: '',
toFilteredCurrencies: [],
setSelectedCurrencyFrom(currency) {
this.selectedCurrencyFrom = currency
},
setSelectedCurrencyTo(currency) {
this.selectedCurrencyTo = currency
},
async getExchangeRate() {
const conn = await fetch(
`https://api.exchangerate-api.com/v4/latest/${this.selectedCurrencyFrom}`
)
const res = await conn.json()
console.log(res)
this.exchangeRate = res.rates[this.selectedCurrencyTo]
},
setFromFilters(string) {
this.fromFilterString = string
if (this.fromFilterString !== '') {
this.fromFilteredCurrencies = this.currencies.filter((currency) =>
currency.toLowerCase().includes(string.toLowerCase())
)
} else {
this.fromFilteredCurrencies = []
}
},
setToFilters(string) {
this.toFilterString = string
if (this.toFilterString !== '') {
this.toFilteredCurrencies = this.currencies.filter((currency) =>
currency.toLowerCase().includes(string.toLowerCase())
)
} else {
this.toFilteredCurrencies = []
}
},
}
}
have pretty much never seen anyone explicitly define an action
Well, this is weird because it is a very common thing to only mutate state through actions to avoid unexpected mutations. In MobX6 actions are enforced by default, but you can disable warnings with configure method:
import { configure } from "mobx"
configure({
enforceActions: "never",
})
a good way to separate the actions into a different file
You don't really need to do it, unless it's a very specific case and you need to somehow reuse actions or something like that. Usually you keep actions and the state they modify together.
I am not quite sure what you are doing with result of createCurrencyStore, are you passing it to observable? Anyway, the best way to create stores in MobX6 is to use makeAutoObservable (or makeObservable if you need some fine tuning). So if you are not using classes then it will look like that:
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
That way every getter will become computed, every method will become action and all other values will be observables basically.
More info in the docs: https://mobx.js.org/observable-state.html
UPDATE:
Since your getExchangeRate function is async then you need to use runInAction inside, or handle result in separate action, or use some other way of handling async actions:
import { runInAction} from "mobx"
async getExchangeRate() {
const conn = await fetch(
`https://api.exchangerate-api.com/v4/latest/${this.selectedCurrencyFrom}`
)
const res = await conn.json()
runInAction(() => {
this.exchangeRate = res.rates[this.selectedCurrencyTo]
})
// or do it in separate function
this.handleExchangeRate(res.rates[this.selectedCurrencyTo])
},
More about async actions: https://mobx.js.org/actions.html#asynchronous-actions

GraphQL with Apollo - how to use fetchMore

I have a problem with understanding Apollo fetchMore method. I'm new to this tool and example in doc is too much complicated for me. I was searching on the internet but I can't find more (current) examples. Can you help me write a most simple example of infinite loading data? I have this code:
const productInfo = gql`
query {
allProducts {
id
name
price
category {
name
}
}
}`
const ProductsListData = graphql(productInfo)(ProductsList)
and I want to fetch 5 products and 5 more after every click on 'Load More' button. I understand the idea of this - cursor e.t.c but I don't know how to implement this. (I'm using React)
For infinite loading, you would use a cursor. Referencing the example on the Apollo documentation, http://dev.apollodata.com/react/pagination.html#cursor-pages
Tailoring this to your schema you've provided, it would look something like this (Untested)
const ProductsListData = graphql(productInfo, {
props({ data: { loading, cursor, allProducts, fetchMore } }) {
return {
loading,
allProducts,
loadMoreEntries: () => {
return fetchMore({
variables: {
cursor: cursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const previousEntry = previousResult.entry;
const newProducts = fetchMoreResult.allProducts;
return {
cursor: fetchMoreResult.cursor,
entry: {
allProducts: [...previousEntry.entry.allProducts, ...newProducts],
},
};
},
});
},
};
},
})(ProductsList);
You'll likely have to play around with the pathing of the objects as this should look similar to your schema. But for the most part this is what your infinite scrolling pagination implementation should look like.
Check out this tutorial by graphql.college https://www.graphql.college/building-a-github-client-with-react-apollo/
Also, you can take a look at my implementation on Github. Specifically, check ./src/views/profile on the query-mutation-with-hoc branch

Resources