Fetch more function is undefined for useLazyQuery react-apollo hook - reactjs

I use react-apollo useLazyQuery hook. My goal to use it in fetch more button click. However, when I try to use it the fetchMore function is undefined.
Here the call to it:
const [getMyData,{ loading, data, fetchMore }] = useLazyQuery(
myQuery,
{
notifyOnNetworkStatusChange: true,
fetchPolicy: 'network-only',
variables: {}
})
Even if I run lazy on component ready, fetchMoreStill undefined.
useEffect(()=>{
getMyData()
},[])
The question is why it is undefined?
"apollo-client": "^2.6.4", "react-apollo": "^3.1.3",

You need to run the loadItem first. My case also loads on demand because I'm using SSR.
Here is my work around, check the fetchMore is undefined to make the decision
function NewItemListingPagination({
initialItems,
initialPage,
initialLimit,
initialTotal,
className
}: ComponentPropTypes) {
const { t } = useTranslation(['homepage', 'common'])
const page = useRef<number>(initialPage)
const [loadItem, { loading, data, fetchMore }] = useLazyQuery(FEED_NEW_ITEM)
async function loadMore(): Promise<void> {
const newPage = page.current + 1
const offset = (newPage - 1) * initialLimit
page.current = newPage
if (!fetchMore) {
loadItem({
variables: {
limit: 12,
offset
}
})
} else {
fetchMore({
query: FEED_NEW_ITEM,
variables: {
limit: 12,
offset
},
updateQuery: (previousResult, {fetchMoreResult}) => {
console.log('dddd', previousResult, fetchMoreResult)
return previousResult
}
})
}
}
const items = !data ? initialItems : initialItems.concat(data.feed.items)
return <div></div>
NOTE: this code may not optimized, I just want to show the flow of fetchMore.

I am also new to react but as far as I know you have to use refetch and than give it a name like this:
const [getMyData,{ loading, data, refetch: fetchMore }] = useLazyQuery...
you can do the same thing with data and loading.
const [getMyData,{ loading: myLoading, data: myData, refetch: fetchMore }] = useLazyQuery...

Related

Pausing react query and re-fetching new data

I have a useQuery which is disabled in a react function component. I have another useQuery that uses mutate and on the success it calls refetchMovies(). This all seems to work well but I'm seeing old data in the refetchMovies. Is there a way for to get the refetchMovies to always call fresh data from the server when its called ?
const MyComponent = () => {
const {data, refetch: refetchMovies} = useQuery('movies', fetchMovies, {
query: {
enabled: false
}
})
const {mutate} = useQuery('list', fetchList)
const addList = useCallback(
(data) => {
mutate(
{
data: {
collection: data,
},
},
{
onSuccess: () => refetchMovies(),
onError: (error) => console.log('error')
}
)
},
[mutate, refetchMovies]
)
return (
<div onClick={addList}> {data} </div>
)
}
Try to invalidate the query in your onSuccess callback instead of manually refetching it:
https://tanstack.com/query/v4/docs/react/guides/query-invalidation
Example:
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries({ queryKey: ['todos'] })

Manually triggering useQuery from useEffect

My app has a modal with a few buttons. When the buttons are clicked, calls are made to the server, which work as expected. However, the screen does not refresh with the changes and I can't figure out how to make it work. I think I need to update the data, which will cause the components to render again, which is why I'm trying to use refetch. FYI - I found this, which helps a lot, but I'm not sure what's wrong with my implementation.
...
import { GetData } from '../../Api/GetData';
...
export const Timeline = ({props}) => {
const [refresh, setRefresh] = useState(true)
const {isLoading, isSuccess, data, refetch} = useQuery("key",
GetData({name: props.name, period: props.period}), {
staleTime: Infinity,
enabled: false
})
useEffect(() => {
if(refresh) {
setRefresh(false)
refetch()
}
}, [refresh, refetch]) // refresh is set to true by child components
if(isLoading) ...
}
export const GetData = async (params) => {
const baseUrl = process.env.REACT_APP_ESPO_BASE_URL
const url = "https://..."
const headers = {"X-Api-Key": process.env.REACT_APP_API_KEY}
const response = await fetch(url, {method: "GET", headers: headers})
return response.json()
}
Any help would be greatly appreciated.
Your problem is that you're executing GetData when you set up useQuery rather than passing in a function. You can improve this by changing your useQuery call to something like
const {isLoading, isSuccess, data, refetch} = useQuery("key",
() => GetData({name: props.name, period: props.period}), {
staleTime: Infinity,
enabled: false
})

Multiple useLazyQuery hooks (Apollo Client) in React Function Component

I am trying to include two Apollo-Client useLazyQuery hooks in my function component. Either works fine alone with the other one commented out, but as soon as I include both, the second one does nothing. Any ideas?
export default function MainScreen(props) {
useEffect(() => {
validateWhenMounting();
}, []);
const [validateWhenMounting, { loading, error, data }] = useLazyQuery(
validateSessionToken,
{
onCompleted: (data) => console.log('data', data),
},
);
const [validate, { loading: loading2, error: error2, data: data2 }] =
useLazyQuery(validateSessionTokenWhenSending, {
onCompleted: (data2) => console.log('data2', data2),
});
const handleSendFirstMessage = (selectedCategory, title, messageText) => {
console.log(selectedCategory, title, messageText);
validate();
};
Figured it out: Adding the key-value pair fetchPolicy: 'network-only', after onCompleted does the trick. It seems that otherwise, no query is being conducted due to caching...
This is the pattern that I was talking about and mentioned in the comments:
const dummyComponent = () => {
const [lazyQuery] = useLazyQuery(DUMMY_QUERY, {variables: dummyVariable,
onCompleted: data => // -> some code here, you can also accept an state dispatch function here for manipulating some state outside
onError: error => // -> you can accept state dispatch function here to manipulate state from outside
});
return null;
}
this is also a pattern that you are going to need sometimes

Using fetchMore to fetch ALL data on component mount

I have a situation where I need to fetch e.g. all articles posted by a user when a component is mounted. To get a user's articles I am using the following query:
const GET_USER_ARTICLES = gql`
query getUserArticles($id: ID, $numArticles: Int!, $cursor: String) {
user(id: $id) {
id
articles(first: $numArticles, after: $cursor, orderBy: "-created", state: "enabled") #connection(key: "userArticles") {
edges {
node {
name
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
`;
If there is a next page I want to keep fetching more articles until I have ALL of them. Up until now I haven't had the need to do anything like this (normally I have a button the user can click "Load more" to fetch more articles for example, but now need to fetch everything without a user interacting with anything), so I'm not sure what the best way to go about this is.
An example of the query in React:
const PAGE_SIZE = 10;
const { data, loading, fetchMore } = useQuery<UserArticlesData, UserArticlesVariables>(
GET_USER_ARTICLES,
{ variables: { id: userId, numArticles: PAGE_SIZE, cursor: null } },
);
I am a little lost how I can use the fetchMore to keep fetching until there aren't any more pages left, while also showing a loading state to the user. I'm also not sure this is the best way to go about this in the first place, so any suggestions are more than welcome!
If the API does not limit the page size, you could just provide an arbitrarily large number as the page size to get the remaining results. Assuming the page size can only be so big, though, you can do something like this:
const { data, loading, fetchMore } = useQuery(GET_USER_ARTICLES, {
variables: { id: userId, numArticles: PAGE_SIZE, cursor: null },
notifyOnNetworkStatusChange: true,
})
const fetchRest = async () => {
const { user: { articles: { pageInfo } } } = data
const updateQuery = (prev, { fetchMoreResult }) => {
// Merge the fetchMoreResult and return the combined result
}
let hasNextPage = pageInfo.hasNextPage
let cursor = pageInfo. endCursor
while (hasNextPage) {
const { data } = await fetchMore({
variables: { id: userId, numArticles: PAGE_SIZE, cursor },
updateQuery,
})
const { user: { articles: { pageInfo } } } = data
hasNextPage = pageInfo.hasNextPage
cursor = pageInfo. endCursor
}
}
By setting notifyOnNetworkStatusChange to true, loading will be updated whenever fetchMore is doing any fetching. Then we just loop until hasNextPage is called. fetchMore returns a Promise that resolves to the query result, so we can use the query response outside the updateQuery function.
Note that this is a rough example -- you might actually want to keep track of loading state yourself, for example. If your API has rate limiting, your logic should account for that as well. However hopefully this gives you a good starting point.
Edit:
If you need to get all the articles initially, I wouldn't use useQuery and fetchMore at all. The easiest workaround would be to manage the data and loading state yourself and utilize client.query instead.
const client = useApolloClient()
const [data, setData] = useState()
const [loading, setLoading] = useState(true)
const fetchAll = async () => {
let hasNextPage = true
let cursor = null
let allResults = null
while (hasNextPage) {
const { data } = await client.query(GET_USER_ARTICLES, {
variables: { id: userId, numArticles: PAGE_SIZE, cursor },
})
// merge data with allResults
hasNextPage = pageInfo.hasNextPage
cursor = pageInfo. endCursor
}
setLoading(false)
setData(allResults)
}
useEffect(() => {
fetchAll()
}, [])

useLazyQuery causing too many re-renders [Apollo/ apollo/react hooks]

I'm building a discord/slack clone. I have Channels, Messages and users.
As soon as my Chat component loads, channels get fetched with useQuery hook from Apollo.
By default when a users comes at the Chat component, he needs to click on a specific channels to see the info about the channel and also the messages.
In the smaller Channel.js component I write the channelid of the clicked Channel to the apollo-cache. This works perfect, I use the useQuery hooks #client in the Messages.js component to fetch the channelid from the cache and it's working perfect.
The problem shows up when I use the useLazyQuery hook for fetching the messages for a specific channel (the channel the user clicks on).
It causes a infinite re-render loop in React causing the app to crash.
I've tried working with the normal useQuery hook with the skip option. I then call the refetch() function when I need it. This 'works' in the sense of it not giving me infinite loop.
But then the console.log() give me this error: [GraphQL error]: Message: Variable "$channelid" of required type "String!" was not provided. Path: undefined. This is very weird because my schema and variables are correct ??
The useLazyQuery does give me infinite loop as said before.
I'm really struggling with the conditionality of apollo/react hooks...
/// Channel.js component ///
const Channel = ({ id, channelName, channelDescription, authorName }) => {
const chatContext = useContext(ChatContext);
const client = useApolloClient();
const { fetchChannelInfo, setCurrentChannel } = chatContext;
const selectChannel = (e, id) => {
fetchChannelInfo(true);
const currentChannel = {
channelid: id,
channelName,
channelDescription,
authorName
};
setCurrentChannel(currentChannel);
client.writeData({
data: {
channelid: id
}
});
// console.log(currentChannel);
};
return (
<ChannelNameAndLogo onClick={e => selectChannel(e, id)}>
<ChannelLogo className='fab fa-slack-hash' />
<ChannelName>{channelName}</ChannelName>
</ChannelNameAndLogo>
);
};
export default Channel;
/// Messages.js component ///
const FETCH_CHANNELID = gql`
{
channelid #client
}
`;
const Messages = () => {
const [messageContent, setMessageContent] = useState('');
const chatContext = useContext(ChatContext);
const { currentChannel } = chatContext;
// const { data, loading, refetch } = useQuery(FETCH_MESSAGES, {
// skip: true
// });
const { data: channelidData, loading: channelidLoading } = useQuery(
FETCH_CHANNELID
);
const [fetchMessages, { data, called, loading, error }] = useLazyQuery(
FETCH_MESSAGES
);
//// useMutation is working
const [
createMessage,
{ data: MessageData, loading: MessageLoading }
] = useMutation(CREATE_MESSAGE);
if (channelidLoading && !channelidData) {
console.log('loading');
setInterval(() => {
console.log('loading ...');
}, 1000);
} else if (!channelidLoading && channelidData) {
console.log('not loading anymore...');
console.log(channelidData.channelid);
fetchMessages({ variables: { channelid: channelidData.channelid } });
console.log(data);
}
I expect to have messages in data from the useLazyQuery ...But instead get this in the console.log():
react-dom.development.js:16408 Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop.
You could use the called variable return by useLazyQuery.
!called && fetchMessages({ variables: { channelid: channelidData.channelid } });
You call fetchMessages on every render.
Try to put fetchMessages in a useEffect :
useEffect(() => {
if (!channelidLoading && channelidData) {
fetchMessages();
}
}, [channelidLoading, channelidData]);
Like that the fetchMessages function only calls when
channelidLoading or channelidData is changing.
You could also look at doing the following:
import debounce from 'lodash.debounce';
...
const [fetchMessages, { data, called, loading, error }] = useLazyQuery(
FETCH_MESSAGES
);
const findMessageButChill = debounce(fetchMessages, 350);
...
} else if (!channelidLoading && channelidData) {
findMessageButChill({
variables: { channelid: channelidData.channelid },
});
}

Resources