How to refresh graphql data on state change - reactjs

import { useQuery, gql, useMutation } from "#apollo/client";
const Questions = () => {
const [modal, setModal] = useState(false)
const QUESTION_QUERIES = gql`
query getQuestions(
$subjectRef: ID
$gradeRef: ID
$chapterRef: ID
$status: String
) {
getQuestions(
subjectRef: $subjectRef
gradeRef: $gradeRef
chapterRef: $chapterRef
status: $status
) {
id
question_info
question_type
answer
level
published
subjectRef
gradeRef
chapterRef
levelRef
streamRef
curriculumRef
options
status
subject
grade
chapter
stream
curriculum
}
}
`;
const { loading, error, data } = useQuery(QUESTION_QUERIES);
return (
<div>
</div>
)
}
Here is my react graphql code.
I wants to fetch data when modal change using state if modal status change to true to false or false to
true it will make api call to fetch questions again
Please take a look how to solve the issue.

use useLazyQuery:
const [updateFn,{ loading, error, data }]= useLazyQuery(QUESTION_QUERIES);.
Then create useEffect with modal as dependency variable, and call updateFn inside useEffect

You want to fetch data after the modal state change, So you simply use useEffect and put modal in the dependency list of the useEffect and for useQuery there is also a function called refetch, the logic would be like this
const { loading, error, data, refetch } = useQuery(QUESTION_QUERIES);
useEffect(() => {
// the reason I put if condition here is that this useEffect will
// also run after the first rendering screen so you need to put a check
// to do not run refetch in that condition
if (data) refetch();
}, [modal]);

Related

ReactQuery make refetch with old data

I use ReactQuery in My App. I create a custom hook with the state. I export function that changes this state in my application. I expect that after changing state I will make a re-fetch with a new state. But it does not happen. I add UseEffect on change state and execute refetch() after my state is changed, but it fetches with previous data, with a body before I updated the state.
So inside UseEffect I see that body is changed(with new data) -> call refetch -> useQuery call ()=>fetchCommissionsData(body), but with old body(not new data)
I'm using: "react": "^18.1.0", "react-query": "^3.39.1"
import React, { useState, useEffect } from 'react'
import { getCommissionsData, getSummary } from "../utils/apis";
import { queryKeys } from "../utils/constants";
import { useMutation, useQueryClient, useQuery } from "react-query";
//REST API
const fetchCommissionsData = async (request) => {
const response = await getCommissionsData(request)
return response
}
export function useCommissionsData() {
const [body, setBody] = useState(null)
const queryClient = useQueryClient()
const searchValues = queryClient.getQueryData(queryKeys.searchValues)
//get data from client,add some extra parameters and update state with request body
const getCommissionsData = (data) => {
const body = {
...data,
movementType: searchValues.movementType,
viewType: searchValues.viewType,
summaryType: searchValues.summaryType
}
setBody(body)
}
//Effect: refetch query to get new commissions data on every change with request body
useEffect(() => {
refetch()
}, [body])
//default data is empty array
const fallback = [];
const { data = fallback, refetch } = useQuery([queryKeys.commissionsData], () => fetchCommissionsData(body), {
refetchOnWindowFocus: false,
enabled: false // turned off by default, manual refetch is needed
});
return { data, getCommissionsData }
The misconception here is that you're thinking in an imperative way: "I have to call refetch if something changes, so I need an effect for that."
react-query however is quite declarative, so you don't need any of that. refetch is meant for refetching with the same parameters. That's why it also doesn't accept any variables as input.
Judging from your code, you want to fetch once body is set. This is best done by:
adding body to the queryKey
disabling the query only when body is null:
const [body, setBody] = useState(null)
const { data = fallback } = useQuery(
[queryKeys.commissionsData, body],
() => fetchCommissionsData(body),
{
enabled: body === null
}
);
Then, all you need to do is call setBody with a new value, and react-query will automatically trigger a refetch with the new body values. Also, it will cache per body, so if you go back from one filter to the other, you will still have cached data.

useEffect not being executed in React

This seems to be a common problem but somehow I simply cannot get it to work : I am trying to read some data from a mongoDB database. If I call the NodeJS server directly in a browser, as in
http://localhost:5000/record/nilan
I get the data as a JSON string:
{
"pid":{
"ck":"F19909120:525.522.8788.37",
"name":"nilan",
"cID":"0SL8CT4NP9VO"
}
}
But when I am calling this from a React function RecordDetails(), I get nothing. Please see the code below :
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useParams, useNavigate } from "react-router";
export default function RecordDetails() {
const params = useParams();
const [record1, setRecords] = useState([]);
window.alert('here1');
// This method fetches the records from the database.
useEffect(() => {
async function get1Record() {
//const id = params.id.toString();
const response = await fetch(`http://localhost:5000/record/${params.id.toString()}`);
if (!response.ok) {
const message = `An error occurred: ${response.statusText}`;
window.alert(message);
return;
}
const record1 = await response.json();
const message2 = 'here'
window.alert(message2);
window.alert(record1.pid.name);
window.alert(record1.pid.ck);
setRecords(record1);
}
get1Record();
return;
} , [record1.length]);
window.alert('here2');
// This following section will display the table with the records of individuals.
return (
<div>
<h3>Record Details</h3>
{record1.pid.name}
{record1.pid.ck}
{record1.pid.cID}
</div>
);
}
The process does not seem to be entering the useEffect() part of the code .
When this function RecordDetails() is called, we get the alerts in this sequence. "here1", "here2", "here1", "here2"; then the functions terminates with a blank screen.
I do not get any of the alerts from inside the useEffect, not even the error message alert. What am I doing wrong?
The useEffect hook only runs after the DOM has rendered and when the values inside the dependency array have changed; since you don't change the values inside the dependency array anywhere except within the useEffect hook, it never runs because record1.length will never change -- the code is never reached. You may want to use the useMemo hook instead, which runs once and then will only change when its dependency changes. Also, in order to get it to update, you need to trigger the change from outside the hook, not within, otherwise it will not run. Thus, may I suggest something along the following:
const userInfo = useMemo(() => {
const response = //make server call
return response
}, [dependentValues]);
setRecords({record1: userInfo});
return (
<div onChange={() => {changeDependentValues()}} />
)

How to derive "loading" from useSWR between fetches without revalidation?

I was asked a question regarding SWRs "loading" state:
How do you create a loading state from SWR between different URL fetches?
Their docs make it appear straight forward:
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
const isLoading = !error && !data;
However, this logic seems to fail after the first render of the hook/component. On the first render data is undefined. Then loads and data becomes a value to consume in the UI.
Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.
There is an additional item returned isValidating. So I updated my logic:
const isLoading = (!data && !error) || isValidating
However, this could be true when:
there's a request or revalidation loading.
So in theory something else causes my component to rerender. This could inadvertently cause a "revalidation" and trigger loading state gets shown. This could break the UI temporarily, by accident.
So how do you derive "loading" between URL changes without revalidation? I am trying to replicate how graphQL Apollo Client returns const { loading, error, data } = useQuery(GET_DOGS);
Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.
data will be undefined again when you change the key (id), if it doesn't have a cache value.
Remember that in SWR { data } = useSWR(key) is mentally equivalent to v = getCache(k), where fetcher (validation) just write to the cache and trigger a re-render.
data is default to undefined, and isValidating means if there's an ongoing request.
Alternatively, you can derive loading through the use of middleware. Here's what I use...
loadingMiddleware.ts
import { useState } from 'react'
import { Middleware } from 'swr'
const loadingMiddleware: Middleware = (useSWRNext) => (key, fetcher, config) => {
const [loading, setLoading] = useState(false)
const extendedFetcher = (...args) => {
setLoading(true)
try {
return fetcher(...args)
} finally {
setLoading(false)
}
}
const swr = useSWRNext(key, extendedFetcher, config)
return { ...swr, loading }
}
export default loadingMiddleware
App.tsx
import { SWRConfig } from 'swr'
import loadingMiddleware from './loadingMiddleware'
const App: FC = () => {
...
return (
<SWRConfig value={{ use: [loadingMiddleware] }}>
...
</SWRConfig>
)
}
export default App
Update (12/13/22)
swr#v2 is out and provides isLoading and isValidating properties in the return value of useSWR.
Here's the difference between the two according to the swr docs.
isValidating becomes true whenever there is an ongoing request whether the data is loaded or not.
isLoading becomes true when there is an ongoing request and data is not loaded yet.

useSWR integration with pagination on backend

When page changes, new query is created and it's data is set to initialData.
In this case user sees initialData before new query data is fetched:
import React from "react";
import fetch from "../lib/fetch";
import useSWR from "swr";
function Component({ initialData }) {
const [page, setPage] = React.useState(1);
const { data } = useSWR(
`/api/data?page=${page}`,
{ initialData }
);
// ...
}
Component.getServerSideProps = async () => {
const data = await fetch('/api/data?page=1');
return { initialData: data };
};
My issue is that initialData is used as a fallback every time I query for new data:
Do you have any ideas on how I can prevent this flicker?
So in react-query, I think there are multiple ways to avoid this:
keepPreviousData: true
this is the main way how to do pagination, also reflected in the docs as well as the examples.
It will make sure that when a query key changes, the data from the previous queryKey is kept on the screen while the new fetch is taking place. The resulting object will have an isPreviousData flag set to true, so that you can e.g. disable the next button or show a little loading spinner next to it while the transition is happening. It is similar in ux what react suspense is going to give us (somewhen).
initialData function
initialData accepts a function as well, and you can return undefined if you don't want initial data to be present. You could tie this to your page being the first page for example:
function Component({ initialData }) {
const [page, setPage] = React.useState(1);
const { data } = useQuery(
['page', id],
() => fetchPage(id),
{ initialData: () => page === 1 ? initialData : undefined }
);
}
keep in mind that you will have a loading spinner then while transitioning, so I think this is worse than approach 1.
since your initialData comes from the server, you can try hydrating the whole queryClient. See the docs on SSR.
initialData works very well with keepPreviousData, so here is a codesandbox fork of the example from the doc where initialData is used (solution 1). I think this is the best take: https://codesandbox.io/s/happy-murdock-tz22o?file=/pages/index.js

What is the best way to mutate remote data fetched with useQuery

I am fairly new to graphQL and Apollo. I hope I can make myself clear:
I am fetching data using the apollo/react-hook useQuery. Afterwards I populate a form with the data so the client can change it. When he is done, the data gets send back to the server using useMutation.
Until now, I use onCompleted to store the fetched data in the component state. That looks like this:
import React, { useState } from 'react';
import { TextField } from '#material-ui/core';
const Index = () => {
const [state, setState] = useState(null)
const {data, loading, error} = useQuery<typeof queryType>(query, {
onCompleted: data => {
// modify data slightly
setState(data)
}
})
return (
<TextField value={state} onChange={() => setState(event.target.value)}/>
)
}
The form than uses the values stored in the component state and the form handlers use setState
to change it.
My question now is, if this is the best practice and if the storing of the fetched data in a local component state neccessary.
Because you don't want to just fetch and render the data -- you want to be able to mutate it when the form values change -- we can't just utilize the data as is. Unless you're using uncontrolled inputs and refs to manage your forms (which you probably shouldn't do), then you're going to need to use some component state.
I think your current approach is mostly fine. The biggest downside is that if the query takes a while (maybe the user has a spotty internet connection), there's a window for them to start filling out the form only to have their input overridden once the query completes. You'd have to explicitly disable the inputs or hide the form until loading completes in order to prevent this scenario.
An alternative approach is to split your component into two:
const Outer = () => {
const {data, loading, error} = useQuery(query)
if (!data) {
return null // or a loading indicator, etc.
}
return <Inner data={data}/>
}
const Inner = ({ data }) => {
const [value, setValue] = useState(data.someField)
<TextField value={value} onChange={() => setValue(event.target.value)}/>
}
By not rendering of the inner component until loading is complete, we ensure that the initial props we pass to it have the data from the query. We can then use those props to initialize our state.
Seems like useQuery only has a state for responseId: https://github.com/trojanowski/react-apollo-hooks/blob/master/src/useQuery.ts
But you get a refetch function from the useQuery as well.
const { loading, error, data, refetch } = useQuery(...);
Try calling refetch(), after you used useMutation() to update the data. It shouldn't trigger a rerender. You probably have to set your own state for that reason.
Maybe something like:
const handleUpdate = () =>{
setData(refetch());
}
Alternativly you get the data after using useMutation which is also in the state: https://github.com/trojanowski/react-apollo-hooks/blob/master/src/useMutation.ts
const [update, { data }] = useMutation(UPDATE_DATA);
data will always be the newest value so you could also do:
useEffect(()=>{
setData(data);
// OR
// setData(refetch());
}, [data])

Resources