How to chain useLazyQuery hook in handler function - reactjs

I want to chain apollo useMutation and useQuery hooks in a handler function. I cannot seem to get the data property of the useLazyQuery hook to alias - or for that matter the mutation properties loading, error, data. I could get the same effect with useMemo, but I want to explore what I can do with the lazy query in a promise chain.
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [requestLogin, { loading, error }] = useMutation(LOGIN_USER)
const [getUserProfile, {
loading: loadingProfile,
error: errorProfile,
data: profileData
}] = useLazyQuery(GET_USER_PROFILE)
const submitHandler = (event) => {
event.preventDefault()
requestLogin({
variables: {
email: email,
password: password
}
}).then(({ data }) => {
console.log("INITIAL DATA", data)
login(data.login.token, data.login.user)
}).then(({ data, profileData }) => getUserProfile({
variables: {
account_id: data.login.user.account.id
}
})).then(profileData => {
console.log("DATA", profileData)
history.push('/')
})
}

Related

Review request: is useEffect an appropriate hook for my custom hook?

I've written a custom hook to query data from a subgraph:
const useAllLPTokens = (): GraphQLResponse<LPTokens> => {
const [status, setStatus] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<any>();
const [response, setResponse] = useState<any>();
const [payload, setPayload] = useState<LPTokens | undefined>();
const getLPTokenData = async () => {
setLoading(true);
try {
const res = await axios.post(subgraphEndpoint,
{
headers: { "Content-Type": "application/json" },
query: graphQuery
}
);
setStatus(res.status);
setResponse(res)
setPayload(res.data)
} catch (error) {
setError(error)
}
setLoading(false);
}
useEffect(() => {
getLPTokenData();
}, [])
return { status, loading, error, response, payload }
}
const Dashboard: React.FC = () => {
const { account } = useActiveWeb3React();
const {
status: status1,
loading: loading1,
error: error1,
response: response1,
payload: payload1
} = useAllLPTokens()
... ...
I was thinking of calling the useAllLpTokens hook every couple of minutes in my Dashboard component, however I'm not sure if what I have written is the best approach if I'm thinking along the lines of memoizing the payload, considering the payload that gets returned from res.data would likely be the same for a while each time. Separately when I use useMemo or useCallback instead of useEffect in the custom hook, it goes into an endless loop of firing itself.
Separately, another problem I face is that Dashboard component renders itself multiple times when I call the custom hook. Is there a way to eliminate that or is this an expected behavior?

Why useState is not accepting input received from the GET request?

I am trying to get an object from the server in the form {Name: true, Place: false, Animal: true, Thing: true} save this data into categoryDetail then extract it using categoryDetail.Name and then pass it to the useState. But somehow useState is not accepting this data.
Here is the code:
const [categoryDetail, setCategoryDetail] = useState({});
useEffect(() => {
axios.get('http://localhost:8080/feeds/category')
.then(response => {
if (JSON.stringify(categoryDetail)
!== JSON.stringify(response.data.category)) {
setCategoryDetail(response.data.category);
}
})
.catch(err => {
console.log(err);
})
})
console.log(categoryDetail.Name); // 👉 this gives ``true``
const [name, setName] = useState(categoryDetail.Name);
const [place, setPlace] = useState(categoryDetail.Place);
const [animal, setAnimal] = useState(categoryDetail.Animal);
const [thing, setThing] = useState(categoryDetail.Thing);
console.log(name); // 👉but here i am getting ``undefined``
(I have commented on the value I am getting)
Please guide me on why is this happening and what to do so that useState accepts the data receive by the server.Also let me know if more information is required.
Try make your code like this
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = () => {
const [categoryDetail, setCategoryDetail] = useState({});
const [name, setName] = useState('');
// const [place, setPlace] = useState('');
// const [animal, setAnimal] = useState('');
// const [thing, setThing] = useState('');
useEffect(() => {
const fetchCategories = async () => {
await axios
.get('https://mocki.io/v1/5a61740b-d272-4943-abe3-908628510020')
.then((response) => {
setCategoryDetail(response.data.categories[0]);
setName(response.data.categories[0].categoryName);
});
};
fetchCategories();
}, []);
// https://mocki.io/v1/5a61740b-d272-4943-abe3-908628510020
return (
<>
<p>{name}</p>
<p>{JSON.stringify(categoryDetail)}</p>
</>
);
};
export default App;
And as you see , I am doing call to setName after fetchCategories() inside of async/await call , put other state setters there
You set default value categoryDetail.Name for useState which will be never modified in renderings (I'm doubting that it's possibly undefined or an error if categoryDetail data is not there)
If you want to get name data from categoryDetail. You can set state after receiving response from useEffect
const [name, setName] = useState(''); //to be safe, set it empty
useEffect(()=>{
axios.get('http://localhost:8080/feeds/category')
.then(response=>{
if (JSON.stringify(categoryDetail) !== JSON.stringify(response.data.category)) {
setName(response.data.category.name); //set `name` into the state
setCategoryDetail(response.data.category);
}
})
.catch(err=>{
console.log(err);
})
})
From your logic, seemingly you're trying to access one by one field from categoryDetail state which is not preferable
If you want to get name, you just simply get it from categoryDetail.Name which is already set in the state
const[categoryDetail, setCategoryDetail]=useState({});
useEffect(()=>{
axios.get('http://localhost:8080/feeds/category')
.then(response=>{
if (JSON.stringify(categoryDetail) !== JSON.stringify(response.data.category)) {
setCategoryDetail(response.data.category);
}
})
.catch(err=>{
console.log(err);
})
})
//make sure your categoryDetail is not undefined
if(categoryDetail) {
console.log(categoryDetail.Name);
}

once the data has been fetched the state value are not getting updated

I'm trying to change the state value once the data has been fetched. I can see that the JSON has been fetched on the network tab but the state value hasn't been changed. State values are logged before the fetch request, I've added await but it hasn't been resolved yet. Do I've to use useEffect for a fetch request, I've tried to use useEffect but it triggers the request once I import this hook is there a workaround?
import axios from 'axios'
import { useState } from 'react'
export const useSignup = () => {
const [loading, setLoading] = useState(true)
const [status, setStatus] = useState(false)
const [msg, setMsg] = useState('')
const registerUser = async (emailAddress, password) => {
try {
await axios
.post('/signup', {
emailAddress: emailAddress,
password: password,
})
.then((res) => {
setStatus(res?.data.success)
setMsg(res?.data.msg)
})
.catch((err) => {
setStatus(err?.response.data.success)
setMsg(err?.response.data.msg)
})
} catch (err) {
console.error(err)
setStatus(false)
setMsg('Error Occured')
} finally {
console.log(msg, status)
setLoading(false)
}
}
return { loading, status, msg, registerUser }
}
You should trigger your function call via a useEffect hook.
Also, if you are using async/await you shouldn't mix it with a Promise-based approach.
Modify the custom hook to accept the two parameters, add the useEffect call and edit your registerUser function:
export const useSignup = (emailAddress, password) => {
const [loading, setLoading] = useState(true);
const [status, setStatus] = useState(false);
const [msg, setMsg] = useState('');
const registerUser = async (emailAddress, password) => {
try {
const { data } = await axios.post('/signup', { emailAddress, password })
setStatus(data.success)
setMsg(data.msg)
} catch (err) {
console.error(err);
setStatus(false);
setMsg('Error Occured');
}
};
useEffect(() => {
registerUser(emailAddress, password);
}, [])
return { loading, status, msg, registerUser };
};
Then you can call your useSignup hook like this
const { loading, status, msg, registerUser } = useSignup('username', 'password')

setState hooks in axios post request not working

const Login=(props)=>{
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState({});
const login = (e)=>{
e.preventDefault();
axios.post('/login',{email, password})
.then(response => {
setUser(response.data)
console.log(response.data, user, "user data");
}).catch(err => {
console.log(err);
})
}
this is the empty user data i
this is my code for the login request its fetching the data and logging it in the console but it doesnt want to set the user
Because setUser is async. So the new state only update when component rerender. You can check by move console.log outside the login function
console.log(user, "user data");
return(...)
You can use useEffect to check state after component rerender:
useEffect(() => {
console.log(user);
}, [user])

implement useFetch react hook to work inside submit function

I have a lot of react experience but I'm new to hooks.
I have the following useFetch hook that I modified after this useAsync hook:
import { useState, useEffect, useCallback } from 'react'
export default function useFetch(url, options, { immediate }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [isPending, setIsPending] = useState(false)
const executeFetch = useCallback(async () => {
setIsPending(true)
setData(null)
setError(null)
await fetch(url, options)
.then((response) => response.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsPending(false))
return { data, error, isPending }
}, [url, options, data, error, isPending])
useEffect(() => {
if (immediate) {
executeFetch()
}
}, [executeFetch, immediate])
return { data, error, isPending, executeFetch }
}
My problem is I want to use it inside a submit function, and hooks don't work inside other functions, like so (reduced version of the code for brevity):
export default function SignupModal({ closeModal }) {
const { executeFetch } = useFetch(url, {options},
{ immediate: false }
)
async function handleSubmit(evt) {
evt.preventDefault()
const { data, error, isPending } = await executeFetch()
}
...
}
currently I'm intentionaly throwing an error in the call, but the error variable remains null.
What am I missing here?
Is this even possible with hooks?
Thanks in advance!
React hook can only be used in the body of your component not inside another function. executeFetch itself is returning { data, error, isPending } and this makes it a nested hook so you can't use it inside your handleSubmit.
useFetch is already returning { data, error, isPending, executeFetch } so executeFetch doesn't need to return again. You can access all these data from the useFetch hook. When you call executeFetch data in your component, data, error and isPending will be updated by setState which will cause your hook to return a new set of values for any of these values that get updated.
export default function useFetch(url, options, { immediate }) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [isPending, setIsPending] = useState(false)
const executeFetch = useCallback(async () => {
setIsPending(true)
setData(null)
setError(null)
await fetch(url, options)
.then((response) => response.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsPending(false))
}, [url, options, data, error, isPending])
useEffect(() => {
if (immediate) {
executeFetch()
}
}, [executeFetch, immediate])
return { data, error, isPending, executeFetch }
}
export default function SignupModal({ closeModal }) {
const { executeFetch, data, error, isPending } = useFetch(url, {options},
{ immediate: false }
)
async function handleSubmit(evt) {
evt.preventDefault()
await executeFetch()
}
...
// Example in your return function
{error != null && <Error />}
<Button state={isPending ? 'processing' : 'normal'}
}
Updated based on the comment
If you need to have an access to data or error inside your handleSubmit function, you will need to return the promise's response/error in your hook so then you should be able to access data/error inside your handleSubmit as well.
Also I recommend to pass options or any other variable data that are subject to change before user triggers handleSubmit to the executeFetch as an argument so executeFetch can always get the latest data.
CodeSandBox Example 1
CodeSandBox Example 2
const useFetch = url => {
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const [data, setData] = useState(null);
const executeFetch = useCallback(
// Here you will access to the latest updated options.
async ({ options }) => {
setIsPending(true);
setError(null);
return await fetch(url, options)
.then(response => response.json())
.then(response => {
setData(response);
return response;
})
.catch(err => {
setError(err.message)
return err;
})
.finally(() => setIsPending(false));
},
[url, setIsPending, setError]
);
return { data, error, isPending, executeFetch }
};
const { data, executeFetch, error, isPending } = useFetch("URL");
const handleSubmit = useCallback(async (event) => {
event.preventDefault();
// I am passing hardcoded { id: 1 } as an argument. This can
// be a value from the state ~ user's input depending on your
// application's logic.
await executeFetch({ id: 1 }).then(response => {
// Here you will access to
// data or error from promise.
console.log('RESPONSE: ', response);
})
}, [executeFetch]);
Another recommendations is to not pass a boolean to trigger executeFetch immediately inside your hook, it's up to the caller to decide whether to run the executeFetch immediately or not.
const { executeFetch, ... } = useFetch(....);
// you can call it immediately after setting the hook if you ever needed
await executeFetch()

Resources