I am trying to change the data every time, url_slug is changing but the data is not refreshing. getting the same data.
const [relatedSnippets,setrelatedSnippets] = useState([]);
const [loading,setLoading] = useState(false);
useEffect(async ()=>{
setLoading(true);
const snippetData = await getData(url_slug);
setrelatedSnippets(snippetData.snippets);
setLoading(false);
},[url_slug]);
async function getData(url_slug) {
var config = {
headers: {
accept: '*/*',
'Content-Type': 'application/json',
'API_ACCESS_KEY': 'hns2V0Ddbkkn8r1XLq3Kw7ZoiBTR0nmA',
}
};
const data = {
slug:url_slug,
}
const url = `http://localhost:8000/api/similarsnippets`;
const snippetData = await axios.post(url,data,config);
const finalData = snippetData.data;
return finalData;
}
The useEffect hook's callback needs to be a synchronous function. Move the async function into the callback and then invoke it.
useEffect(() => {
const loadData = async () => {
setLoading(true);
const snippetData = await getData(url_slug);
setrelatedSnippets(snippetData.snippets);
setLoading(false);
};
loadData();
}, [url_slug]);
Related
I am fetching json data from my local api but can't seem to assign it to the err state.
const [err, setErr] = useState({});
const host = "http://localhost:9000";
const addNote = async (obj) => {
const res = await fetch(`${host}/api/notes/addnote`, {
method : 'POST',
headers : {'Content-Type': 'application/json', 'auth-token' : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiQGdtYWlsLmNvbSIsImlhdCI6MTY3MTQ0ODMyOH0.sNTvl4L9HFaGPOmuSKpJMu418axsUmgDib-94ked3lQ'},
body : JSON.stringify(obj)
});
const data = await res.json();
console.log(data);
setErr(data);
console.log(err);
}
On logging data I get => {title:{msg :''}, description:{msg:''}}
On logging err I get => {}
useState() hook is asynchronous and will not reflect the update immediately. The value get's update in the next render and you can verify that with useEffect hook as shown below
useEffect(() => {
console.log(err) // prints the updated value
}, [err])
If you want the update to reflect immediately, you can use useRef() instead of useState().
Updated answer
I don't recommend using useRef() as it would force the updates between the renders which in turn effects the performance. If you want the errors to be displayed based on backend response, then don't render the component until you receive a response. Refer below code snippet
const [err, setErr] = useState({});
const [isDataLoading, setIsDataLoading] = useState(false);
const host = "http://localhost:9000";
const addNote = async (obj) => {
setIsDataLoading(true); // update loading state
const res = await fetch(`${host}/api/notes/addnote`, {
method : 'POST',
headers : {'Content-Type': 'application/json', 'auth-token' : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiQGdtYWlsLmNvbSIsImlhdCI6MTY3MTQ0ODMyOH0.sNTvl4L9HFaGPOmuSKpJMu418axsUmgDib-94ked3lQ'},
body : JSON.stringify(obj)
});
const data = await res.json();
setErr(data);
setIsDataLoading(false); // update loading state
console.log(data);
}
return (
{
isDataLoading ? <Loading /> : <YourComponent /> // if isDataLoading is true render some loading symbol else render your actual component.
}
)
const [err, setErr] = useState({});
const host = "http://localhost:9000";
const addNote = async (obj) => {
try {
const res = await fetch(`${host}/api/notes/addnote`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"auth-token":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiQGdtYWlsLmNvbSIsImlhdCI6MTY3MTQ0ODMyOH0.sNTvl4L9HFaGPOmuSKpJMu418axsUmgDib-94ked3lQ",
},
body: JSON.stringify(obj),
});
const data = await res.json();
if (res.status !== 200) {
// need to check object and set error as per object mappig
setErr(res.data);
}
} catch {
setErr('Api not workoing');
}
console.log(err);
};
in my functional component I want to fetch data once the component mounts. But unfortunately, the request gets fired three times, until it stops. Can you tell me why?
const [rows, setRows] = useState<any[]>([]);
const [tableReady, setTableReady] = useState<boolean>(false);
const [data, setData] = useState<any[]>([]);
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
})
.then(res => res.json())
.then((result) => {
setData(result);
})
.catch(console.log)
}
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
setRows(rows => [...rows, (createData(convertedId, element.user))]);
});
setTableReady(true);
}
}
}, []);
return (
<div className={classes.root}>
<MUIDataTable
title={""}
data={rows}
columns={columns}
/>
</div>
);
I updated my question due to the comment.
The useEffect is missing a dependency array, so its callback is invoked every time the component renders.
Solution
Add a dependency array.
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
rows.push(convertedId);
});
setTableReady(true);
}
}
}, []); // <-- dependency array
An empty dependency array will run the effect once when the component mounts. If you want it to ran when any specific value(s) update then add these to the dependency array.
See Conditionally firing an effect
Edit
It doesn't appear there is any need to store a data state since it's used to populate the rows state. Since React state updates are asynchronously processed, and useEffect callbacks are 100% synchronous, when you call getData and don't wait for the data to populate, then the rest of the effect callback is using the initially empty data array.
I suggest returning the fetch request from getData and just process the response data directly into your rows state.
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
return fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
});
}
useEffect(() => {
if (!tableReady) {
getData()
.then(res => res.json())
.then(data => {
if (data.length) {
setRows(data.map(element => createData(+element.id, element.user)))
}
})
.catch(console.error)
.finally(() => setTableReady(true));
}
}, []);
I am trying to use a http hook in another component to send a get request. The post request is working fine. But when I try a get request I just get back 'true' when I console log my result. When I send the same get request in postman I get the correct data back, so it isn't a backend problem.
The hook:
import { useState, useCallback, useRef, useEffect } from "react";
export const useHttpClient = () => {
const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState();
const [success, setSuccess] = useState(false);
const activeHttpRequests = useRef([]);
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const httpAbortController = new AbortController();
activeHttpRequests.current.push(httpAbortController);
try {
setErrors();
setSuccess(false);
const response = await fetch(url, {
method: method,
body: body,
headers: headers,
signal: httpAbortController.signal,
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortController
);
if (response.status !== 200) {
setErrors(responseData);
return responseData;
} else {
setSuccess(true);
return true;
}
} catch (err) {
//setErrors(err.message);
setErrors([
"There was an error submitting your form, please try again later.",
]);
setIsLoading(false);
throw err;
}
},
[]
);
//useEffect can also be used for cleanup
useEffect(() => {
return () => {
activeHttpRequests.current.forEach((AbortController) =>
AbortController.abort()
);
};
}, []);
return { isLoading, errors, sendRequest, success };
};
The server call:
useEffect(() => {
const fetchFaq = async () => {
try {
const responseData = await sendRequest(
"http://localhost:8000/api/myEndpoint"
);
console.log(responseData);
setLoadedFaq(responseData);
} catch (err) {}
};
fetchFaq();
}, [sendRequest]);
Your hook returns true if it gets a 200 response code:
if (response.status !== 200) {
setErrors(responseData);
return responseData;
} else {
setSuccess(true);
return true;
}
It only returns responseData if it gets a non-200 code. Just return the data from the hook..
My first attempt at a custom hook is now looping like crazy, I don't fully understand why do I need to test to see if it has something saved in response or error then make the call if it has not?
import React, { useContext } from 'react';
import { Context } from "../components/context";
const useFetch = (url: string, bearer: string, method: string, body: any) => {
const { global } = useContext(Context) as {global: any};
let headers = {'cache-control': 'no-cache', 'Content-Type': 'application/json' };
if (bearer) headers = {...headers, ...{'Authorization': bearer}}
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const apiUrl = global.apiUrl;
React.useEffect(() => {
const fetchData = async () => {
try {
let res;
if (method === 'GET') res = await fetch(apiUrl + url, {method, headers});
else res = await fetch(apiUrl + url, {method, headers, body});
setResponse(await res.json());
} catch (error) {
setError(error);
}
};
fetchData();
}, [apiUrl, body, headers, method, url]);
return { response, error };
};
export { useFetch }
I'm calling it with
import { useFetch } from '../hooks/fetch';
const res = useFetch('http://api.domain.com', '', 'GET', '')
console.log(res);
Each time you unconditionally change state in useEffects it will loop endlessly because it is calling after each state change, you change state again and again useEffects is calling and so on...
There should be some flag probably, and you fetch data only if flag is not true
const [isLoaded, setIsLoaded] = React.useState(false);
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const apiUrl = global.apiUrl;
React.useEffect(() => {
const fetchData = async () => {
if (isLoaded) return
try {
let res;
if (method === 'GET') res = await fetch(apiUrl + url, {method, headers});
else res = await fetch(apiUrl + url, {method, headers, body});
setResponse(await res.json());
setIsLoaded(true)
} catch (error) {
setError(error);
}
};
useEffect gets called anytime one of its dependencies changes. You put in [apiUrl, body, headers, method, url]. Both headers and apiUrl are local variables and recreated everytime the hook is called. This means that the references to those variables will change everytime your useFetch executes.
The useEffect sets a state on success or error which causes the re-render which causes the recreation of those variables which causes the useEffect to be called again.
I recommend removing both of those from the dependency array and moving the variables into your useEffect call as they are only used inside there anyways.
I have a custom hook that I'm using to make API requests on my react front-end application but the hook seems to be having a bug.
It makes API requests as intended but whenever I unmount the current container/page in which the request is being made, my hook doesn't know that the page has been unmounted so it doesn't cancel the request and therefore react throws the 'Can't perform a React state update on an unmounted component' warning.
export function useFetch(initialValue, url, options, key) {
const [response, setResponse] = useLocalStorage(key, initialValue);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const isMounted = { state: true };
async function fetchData() {
setLoading(true);
try {
const res = await axios({
url: url,
baseURL: BASE_URL,
cancelToken: source.token,
...options
});
if (res.data.results) {
setResponse(res.data.results);
} else {
setResponse(res.data);
}
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}
if (isMounted.state) {
fetchData();
}
return () => {
isMounted.state = false;
source.cancel('Operation canceled by the user.');
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
return [response, { error, loading }];
}
By now you are checking for if(isMounter.state) in wrong place. It's currently very next step after you've initialized it.
I believe it should be
const isMounted = { state: true };
async function fetchData() {
setLoading(true);
try {
const res = await axios({
url: url,
baseURL: BASE_URL,
cancelToken: source.token,
...options
});
if(!isMounted.state) return;
.....
}
}
fetchData();
BTW you don't have to use object there: isMounted = true/isMounted = false will work just fine through closure.
Actually your have 2 different approaches mixed: using flag(isMounted) and cancelling request. You may use just one. Cancelling request should work(as far as I see) but it leads your catch block is executed:
} catch (error) {
setError(error);
setLoading(false);
}
See, unmounting cancels request, but your code still tries to set up some state. Probably you better check if request has been failed or canceled with axious.isCancel:
} catch (error) {
if (!axios.isCancel(error)) {
setError(error);
setLoading(false);
}
}
And you may get rid of isMounted in this case.
I use the following hook to get an ifMounted function
const useIfMounted = () => {
const isMounted = useRef(true)
useEffect(
() => () => {
isMounted.current = false
},[]
)
const ifMounted = useCallback(
func => {
if (isMounted.current && func) {
func()
}
},[]
)
return ifMounted
}
Then in your code add const ifMounted = useIfMounted() to useFetch and before your set functions do ifMounted(() => setLoading(true), ifMounted(() => setError(error)), etc....
Here's a blog post I wrote on the subject: https://aceluby.github.io/blog/react-hooks-cant-set-state-on-an-unmounted-component