I am getting too many re-renders while using react-hooks.
I am trying to fetch data from api by using a parameter in URL.
Here's the code:
export default function Details() {
const { title } = useParams();
const [loading, setLoading] = useState(true);
const [details, setDetails] = useState([]);
const [error, setError] = useState("");
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`API`
)
.then((res) => {
let result = res.data.results;
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(details);
})
.catch((err) => {
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
getDetails(title)
return(
// template
)
now I think this is happening at the line where I call getDetails.
then I tried using useEffect to load the data only once after it is mounted,
useEffect(() => {
getDetails(title);
}, []);
It still is unable to fetch the data, as the getDetails function is never called.
How can I resolve this?
Edit:
Fixed one silly error.
Here's the codesandbox link:
Codesandbox
There are multiple issues with this, first you need to specify what you want to be notified about when the useEffect gets called again. You could do this by adding the variables you want within the array
useEffect(() => {
getDetails(title);
}, [
// Add what you want here
]);
The second issue you have is that you declared the detalis variable twice. Once using the set state here: const [details, setDetails] = useState([]);
The second time here:
const details = getDetails(title)
the code here has two obvious error beside the functionality problems you mentioned:
1 - you cannot declare two variables with same name using let or const; it will throw a SyntaxError
const [details, setDetails] = useState([]);
...
const details = getDetails(title)
2- getDetails function is written with a asynchronous mindset, and it will return nothing,
so details in const details = getDetails(title) will be set to undefined
Looks like your getDetails function has title param so I would add title and getDetails both in the dependency list of useEffects
Or place getDetails inside the useEffect
Here is your working code. You had multiple problems where res.data was undefined so you need to get res.results directly based on your response object
useEffect(() => {
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`https://api.jikan.moe/v3/search/anime?q=${keyword}&page=1&genre_exclude=0`
)
.then((res) => res.json())
.then((res) => {
console.log(res.results);
let result = res.results;
console.log(result);
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(3);
})
.catch((err) => {
console.log(err);
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
console.log('calling getDetails')
getDetails(title);
}, [title]);
Note: tested in the code sandbox link provided in the question. Its working code.
Related
Here simply I am fetching data from mysql DB and storing it in state and in order to fetch this data:
const [orders, setOrders] = useState([]);
To fetch data I am using different functions and finally I am calling those functions using useEffect simple enough and so for everything is working perfectly but the problem comes whenever I use the state as dependency where I am storing data beacause if I dont do that then I have to manually refresh the page for latest changes and I have tried every given solution on stackoverflow but any of the solution didnt work so someone can please help me how can I use this state as dependencey without causing infinite loop:
const [orders, setOrders] = useState([]);
const loadData = async () => {
const response = await fetch("http://localhost/k-shop/load.php");
const result = await response.json();
setOrders(result);
};
const loadTotal = async () => {
const response = await fetch("http://localhost/k-shop/amount.php");
const result = await response.json();
setTotal(result);
};
useEffect(() => {
loadData();
loadTotal();
}, [orders]);
console.log(orders);
If you move the state into the useEffect dependency, you can then check if it is empty, and only set it when that check passes.
It will set the state once to populate and not pass the check again.
const [orders, setOrders] = useState([]);
const loadData = async () => {
const response = await fetch("http://localhost/k-shop/load.php");
const result = await response.json();
setOrders(result);
};
const loadTotal = async () => {
const response = await fetch("http://localhost/k-shop/amount.php");
const result = await response.json();
setTotal(result);
};
useEffect(() => {
if(orders.length === 0) {
loadData();
}
// you can do the same with checking loadTotal() state
}, [orders]);
console.log(orders);
Avoid ,non-primitive data types in dependencyArray ,
useEffect(() => {
loadTotal();
loadData();
}, [total, orders.length]);
every times you "setOrders" means you change the state,every times you change the state,means the "useEffect" will do again.that cause infinite loops.why not try useEffect(() => {loadData()}, [])?
I have a component that updates a piece of state but I'm having issues with it
I have the state declared
const [data, setData] = useState([]);
Then in my useEffect I am
useEffect(() => {
const fetchData = async () => {
await axios
.get(
API_URL,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': API_KEY
},
params:{
"titleId": id
}
}
)
.then((response) => {
setData(response.data.Item);
})
.catch((err) => {
console.error("API call error:", err.message);
});
}
fetchData();
}, [data, id])
If I declare "data" in my dependencies, I get an endless loop of requests which is obviously no good. But if I leave 'data' out from the dependencies it shows nothing, though I am successfully retrieving it in my network's tab and even when I {JSON.styringify(data)} in a div tag aI get the json content too. So the info is in the DOM, but it's not updating the components
How can I do this so I can make an initial request to load the data and not thousands of them?
I've tried the following:
a setTimeout on the callback function
the isCancelled way with a return (() => { callbackFunction.cancel(); })
And there is an Abort way of doing this too but I can't figure it out. Every example I've seen is for class components
Sorry for the vague code. I can't replicate this without lots of coding and an API. Thanks in advance
You want to set the state and then check if is different. I use a custom hook for this which uses the useRef hook:
export function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
const prevData = usePrevious(data);
I don't know what your data looks like, but build a conditional from it. Inside of your useEffect you'll need something like:
if (data !== prevData) fetchData()
or
if (data.id !== prevData.id) fetchData()
You'll then add prevData to you dependencies:
[data, prevData, id]
So useEffects works with dependency.
With dependency - on changing dependency value useEffect will trigger
useEffect(() => {
// code
}, [dependency])
With empty brackets - will trigger on initial of component
useEffect(() => {
// code
}, [])
Without dependency and Brackets - will trigger on every state change
useEffect(() => {
// code
})
Do something like this, if that can help. I also used async/await so you can check that.
const App = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(API_URL, {
headers: {
'Content-Type': 'application/json',
'X-API-KEY': API_KEY,
},
params: {
titleId: id,
},
});
setData(response.data.Item);
} catch (err) {
console.error('API call error:', err.message);
}
};
fetchData();
}, [id]);
if (!data.length) return null;
return <p>Yes, I have data</p>;
};
obviously you will get an infinit loop !
you are updating the data inside your useEffect which means each time the data changes, triggers useEffect again and so on !
what you should do is change your dependencies depending on your case for example :
const [data, setData] = useState([])
const [fetchAgain, setFetchAgain] = useState(false)
useEffect(()=> {
fetchData();
}, [])
useEffect(() => {
if(fetchAgain) {
setFetchAgain(false)
fetchData();
}
}, [fetchAgain])
now each time you want to fetch data again you need to update the fetchAgain to true
I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.
async function getData() {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
tmpArrData = data;
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
function App() {
const [arrData, setArrData] = useState();
const [loadData, setLoadData] = useState(false);
useEffect(() => {
console.log("if it works, this line should be shown");
const tmpArrData = getData();
setArrData(tmpArrData);
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
The useEffect hook is guaranteed to run at least once at the end of the initial render.
getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.
The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.
async function getData() {
return await fetch(".....")
.then((res) => res.json());
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData().then((data) => {
setArrData(data);
});
}, []); // <-- empty dependency so effect called once on mount
Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.
const [arrData, setArrData] = useState([]);
...
const data = arrData[0] || {}; // data is at least an object
return (
<GifCompoment
id={data.id}
name={data.name}
activeTimeTo={data.activeTimeTo}
activeTimeFrom={data.activeTimeFrom}
requiredPoints={data.requiredPoints}
imageUrl={data.imageUrl}
/>
);
You should call state setter insede of Promise
function App() {
const [arrData, setArrData] = useState();
function getData() {
fetch("/api/hidden")
.then((res) => res.json())
.then((data) => setArrData(data));
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData();
}, []);
return ...
}
By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love
useEffect(() => {
console.log("if it works, this line should be shown");
const getData = async () => {
await fetch("hidden API")
.then((ref) => ref.json())
.then((data) => {
setArrData(data);
});
}
getData();
}, []);
function App() {
const [arrData, setArrData] = useState([]);
const [loadData, setLoadData] = useState(false);
const async getData=()=> {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
setArrData(tmpArrData);
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
useEffect(() => {
console.log("if it works, this line should be shown");
const callApi =async()=>{
await getData();
}
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
Page will be rendered three to four times it's normal.
I'm trying to update state immediately after data comes in from the API. The data is coming in, I can see it using the console.log right below my API request. All of the data is right but for some reason, setState never sets in my hook. It just returns and empty array even after the console displays data.
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(response)
}
}
fetchData();
}, []);
I must be doing something wrong but I can't figure out what that is. Hoping someone on here has run into the same issue.
UPDATE: I just changed everything over the a class and duplicated the exact code on another file and ran into the exact same issue. The console updates with the data, but the setState on the line immediately after the data does not setState.
async componentDidMount() {
if (Object.keys(this.props.match.params).length > 0) {
const response = await ApiService.getExperiences(this.props.match.params.experieneId);
console.log(response[0])
this.setState({ experienceData: response[0], occurrenceData: response[0].occurrences });
}
}
You have to useSetState in a proper way, the issue is in the setExperienceData
const [experienceData, setExperienceData] = useState({response:""});
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(props.match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(experienceData => ({ ...experienceData, response: response }));
}
}
fetchData();
}, []);
return(<div>check {experienceData.response}</div>)
I see you left the dependency array empty. This tells React to run this effect only once: when the component first renders. If you want your useEffect to respect your state hook, put setExperienceData inside the dependency array
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
fetchData();
}, [props.match.params]);
const async fetchData = () => {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData([...response])
}
}
Could you please try passing [match.params] as the second argument to your useEffect.
In order to get data from my AWS Postgres DB, I have to first get an AWS Access Token and pass it into the GET call. To accomplish this, in my React app, I've created a file called requests.js into which I plan to build a number of functions. Here are the first two:
// Custom hook to get AWS Auth Token
export const useGetAwsAuthToken = () => {
const [data, setData] = useState();
useEffect(() => {
const fetchData = async function() {
try {
const config = {
headers: { "Authorization":
await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
})
}
};
setData(config);
} catch (error) {
throw error;
} finally {
}
};
fetchData();
}, []);
return { data };
};
// Custom hook for performing GET requests
export const useFetch = (url, initialValue) => {
const [data, setData] = useState(initialValue);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async function() {
try {
setLoading(true);
const response = await axios.get(url);
if (response.status === 200) {
setData(response.data);
}
} catch (error) {
throw error;
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { loading, data };
};
I was under the impression that I could use const [data, setData] = useState(); in both of these functions and that they would be independent of each other. However, back where I call the functions, my IDE is telling me that "data has already been declared" with the 2nd call:
const {data} = useGetAwsAuthToken();
const {loading, data} = useFetch('https://jsonplaceholder.typicode.com/posts');
Furthermore, say I comment out the 2nd line of code above and make this call:
const {data2} = useGetAwsAuthToken();
This leaves data2 as undefined. This is also confusing because shouldn't I be able to have any named return value variable in the calling function?
First, the one that is easier to answer for me: const {data2} = useGetAwsAuthToken isn't valid because you're using destructuring and it's expecting the value of data. So what you're telling it is the equivalent of saying const data2 = useGetAwsAuthToken().data2 What you actually want is const { data: data2 } = useGetAwsAuthToken(). This will take the value that is returned (data) and save it to the current scope as data2.
Now for the first issue you brought up. Why are you using react life cycles in what appears to just be a function? You don't need to save things in state when it's just a function that's not returning a React Component.