Use `useQuery` hook the same way as `useMutation` fails - reactjs

Using React and Apollo React hooks
import { useCallback, useState } from "react";
import { useMutation, useSubscription, useQuery } from "#apollo/react-hooks";
I got a function useChannel in which I have hooks with all my graphql mutations.
Each mutation is imported and then declared as a constant like so:
const [addExportChannel] = useMutation(AddExportChannelMutation);
Which I consume in the returned function of the hook like so
const onAddImportChannel = useCallback(
async (props) => {
try {
const data = await addImportChannel({
variables: {
shopId,
name: props.name,
url: props.url,
filetype: props.filetype
}
});
...
When I try to do the same with useQuery it fails saying useQuery is not iterable.
TypeError: useQuery is not a function or its return value is not iterable
const [FeedProcess] = useQuery(GetFeedProcessQuery);
...
const onFeedProcess = useCallback(
async (props) => {
try {
const data = await FeedProcess({
variables: { shopId, feedId: props.feedId }
});
...
What am I missing here, trying to figure out what is so different in the two hooks.

Because useQuery is called immediately you cannot store it like this (const [FeedProcess] = useQuery(GetFeedProcessQuery);). But apollo's got useLazyQuery that can be stored like you did. If you'd like to store and invoke it later, use useLazyQuery instead.

You should de-structure the props from useQuery hook like so:
const {data, loading, error} = useQuery(GetFeedProcessQuery);

Related

Mock useFetch hook with Jest

Im having this functional component with the useFetch hook:
function Foo(){
const { data, isPending, run } = useFetch(`http://localhost:8080/users`);
}
How can I mock the data const here without doing an API call? The useFetch hook is from the library react-async
import React from "react";
import reactAsync from "react-async";
jest.mock('reactAsync');
it("test", async () => {
const data = []
const isPending = false
const run = null
reactAsync.useFetch.mockReturnValueOnce({ data, isPending, run });
});
see here for other ways

Invalid hook call. Hooks can only be called inside of the body of a function component when i call useQuery in useEffect

I am using apollo-graphql in my react project and i am getting error of
Invalid hook call. Hooks can only be called inside of the body of a function component
Here is my code for this
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
// **************** COMPONENTS ****************
import { GET_MORTGAGE_JOURNEY } from "../../../../Graphql/Journeys/query";
export default function index() {
const insuranceId = useSelector((state) => state.mortgage.insuranceId);
// Panels Heading to show on all panels
useEffect(() => {
if (insuranceId) {
getMortgageData(insuranceId);
}
}, [insuranceId]);
function getMortgageData(insuranceId) {
const { loading, error, data } = useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
});
console.log(data);
}
return <section className="mortage-journey"></section>;
}
Once i run this i get the error, I know that useQuery itself is a hook and i cant call it from inside useEffect, but then what should be the workaround for this as i need insuranceId from my redux state first and send it to the query.
Thanks !
You are breaking the rule of hooks when you call it from any place other than the top level of a React component.
useEffect takes a callback function, and you are calling your hook from that. It is a problem.
I found this skip option in useQuery which helps you call useQuery conditionally.
useEffect(() => {
const { loading, error, data } =
useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
skip : (!insuranceId)
});
}, [insuranceId]);
Any time insuranceId changes your callback runs, so it is run after mount once and then on subsequent changes.
Try using refetch. Something like this:
const { data, refetch } = useQuery(MY_QUERY);
useEffect(() => {
refetch();
}, id);
You can use refetch wherever you like, in useEffect or a button click onclick handler.
Alternatively, you could use useLazyQuery if you don't want it to run the first time:
const [goFetch, { data }] = useLazyQuery(MY_QUERY);
Now you can use goFetch or whatever you want to call it wherever you like.
Your whole example might look like:
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { GET_MORTGAGE_JOURNEY } from "../../../../Graphql/Journeys/query";
export default function index() {
const insuranceId = useSelector((state) => state.mortgage.insuranceId);
const { loading, error, data, refetch } = useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
});
useEffect(() => {
if (insuranceId) {
refetch({id: insuranceId});
}
}, [insuranceId]);
return <section className="mortage-journey"></section>;
}

can i memoize custom hook which is graphql query in react?

// useMe.tsx //
import { gql, useQuery } from "#apollo/client";
import { meQuery } from "../__generated__/meQuery";
export const ME_QUERY = gql`
query meQuery {
me {
id
email
my_workspaces {
title
}
}
}
`;
export const useMe = () => {
return useQuery<meQuery>(ME_QUERY, {
fetchPolicy: "cache-first"
});
};
This is my custom hook. I am trying to get user info with graphql query and made it to custom hook.
But there was some problem, whenever the high-level components state changed,
the low-level component which calling custom-hook(useMe) always rerendered
so i wanna memoize custom hook with useCallback or useMemo but i can't find right answer
please help me, Thank you
Just wrap the value with useCallback is fine
const useMeQuery = () => {
return useQuery<meQuery>(ME_QUERY, {
fetchPolicy: "cache-first"
});
export const useMe = useCallbak(useMeQuery)
}

reactjs-how to get axios response data from parant component to a child component

I've defined a component for requesting data from server and getting the result by axios and I want to use that component to whole of my app for sending requests,I'm going to pass an api link and body to that component and get response from that and I must pass result to my child component!
imagine use clicks on login button,child component sends username and password to parent component(axios component) and will get response data from that
I want to show spinner while getting the data it's important to me!
thanks to everybody!
If you are using a functional components, then it's a good place to use hooks
It can be something like this:
import React, { useState } from 'react';
import axios from 'axios';
export const useRequest = () => {
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(true);
const [data, setData] = useState(null);
const yourAxiosRequest = () => {
//implementation of your axios request
}
const requestHandler = async (url, data) => {
setIsLoading(true);
try {
await const response = yourAxiosRequest({
url,
data,
method: 'POST',
});
setData(response.data);
} catch (e) {
setIsError(true);
} finally {
setIsLoading(false);
}
}
return {
isError,
isLoading,
data,
requestHandler
};
};
and then in your components you can use it like this
import React from 'react';
import { useRequest } from 'common/useRequest';
//your component code
const { isLoading, isError, data, requestHandler } = useRequest();
const clickHandler = (data) => requestHandler(url, data);
//your component code
I will try to explain this using the example you have given, i.e login and loading spinner.
It is ideal for you to use a state management library like redux. It will be the one responsible to handle the data for you. From what I can make from your question, it is called data-binding.
Please check out this for further insight: React Redux getting started

what is the equivalent of this.props.history.push('/some_route') of react class components in hooks?

I am trying to accomplish this
import {isAuthorized} from '../somewhere'
async componentDidMount() {
const authorized = await isAuthorized()
if(!authorized){
this.props.history.push('/login')
}
}
in react hooks, how can I achive this exact functionality, thanks
You can use useHistory hook to get access to the history instance and call history.push to inside a useEffect to navigate to the desired route.
import {isAuthorized} from '../somewhere';
import { useHistory } from "react-router-dom";
function SomeComponent(props) {
const history = useHistory()
useEffect(() => {
const navigate = async () => {
const authorized = await isAuthorized();
if(!authorized){
history.push('/login')
}
}
// call the async function
navigate()
}, [])
}
Keep in mind React doesn't allow the callback for useEffect to be an async function because of possible race conditions. So you have to define a new async function inside the useEffect hook and invoke it.
useEffect(() => {
const apiCall = async () => {
const authorized = await isAuthorized()
if(!authorized){
props.history.push('/login');
}
}
apiCall()
},[])
You need to wrap your await in an async function and call that async function apiCall in the body of the useEffect function.
You will need to use useEffect hook:
useEffect(async () => {
const authorized = await isAuthorized()
if(!authorized){
props.history.push('/login') // note: not this.props but simply props
}
},[]) // empty array to call effect in mounted period.
But react doesn't allow to perform async action directly in useEffect hook. You could wrap an async function inside that:
useEffect(() => {
(async () => {
//...
})()
},[])
For further detail, you may take a look into my another post.
I think in this case you could use useHistory hook which is used like this:
import React from 'react'
import { useHistory } from 'react-router-dom'
export default props => {
const history = useHistory()
React.useEffect(() => {
const isLogged = async () => {
const authorized = await isAuthorized()
return authorized
}
const authorized = isLogged()
if(!authorized)
history.replace('/login') // to replace the route , in order to push : history.push('/login')
},[])
}
EDIT:
Yes, it cannot be made async, I changed the answer. Sorry.
There is one more alternative which comes from redux-react-hooks library's useDispatch() hook. It generally is used to call the store actions which in turn call's the store reducer but you can also pass push method defined in react-router-redux for passing in your routes/pathname data.
import {isAuthorized} from '../somewhere';
import {useDispatch} from "redux-react-hook";
import {push} from "react-router-redux";
async componentDidMount() {
const authorized = await isAuthorized();
const dispatch = useDispatch();
if(!authorized){
dispatch(push('/login'));
}
}

Resources