React useEffect memory leak, second argument - reactjs

I am fetching data inside of useEffect, with intention to update useState with data obtained. I kept getting null inside of oneCrypto state value even though console log showed that data was received. Realized it has to do with second argument missing in useState. When add [] empty array, my oneCrypto shows null. When I set [oneCrypto] inside the array, as a dependency, my app crashes - too many requests, console log prints data received over and over and I don't understand why... help please.
import React, { useState, useEffect } from "react"
import { useParams } from "react-router-dom"
export default function SingleCrypto() {
const [loading, setLoading] = useState(false)
const [oneCrypto, setOneCrypto] = useState(null)
const { id } = useParams()
useEffect(() => {
async function getOneCrypto() {
try {
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const response = await fetch(proxyurl +
"https://pro-api.coinmarketcap.com/v1/cryptocurrency/info?id=" +
id,
{
headers: {
}
}
)
const data = await response.json()
const mydata = data.data;
setOneCrypto(mydata)
console.log(oneCrypto)
} catch (error) {
console.log(error)
}
}
getOneCrypto()
}, [oneCrypto])
return <>
<h1>I am Single Crypto page</h1>
</>
}

Calling setOneCrypto causes a rerender and since oneCrypto has changed since the previous render useEffect is called again and the process restarts. Inside the useEffect where you call console.log(oneCrypto) is happening before the value has been updated because the update happens between renders.
Try removing oneCrypto from the array passed in the second argument and call console.log outside your useEffect.

Your loop is:
1) |--> your async function call setOneCrypto ---|
2) |-- new value of oneCrypto call useEffect <--|
Might be you want one request if oneCrypto is null:
!oneCrypto && getOneCrypto();

Related

My custom React hook method "useFetch" is running 8 times when called

Hope anyone is able to help me with a custom react hook.
My custom react hook "useFetch" is running 8 times when called.
Can anyone see, why it is running 8 times when the custom "useFetch" hook is called?
I am a bit new to React, but it seems like I am using useEffect method wrong. Or maybe I need to use another method.
UseFetch hook method:
import React, { useState, useEffect } from "react";
export const useFetch = function (
options = {
IsPending: true,
},
data = {}
) {
// load data
const [loadData, setLoadData] = useState(null);
// pending
const [isPending, setIsPending] = useState(false);
// error
const [isError, setIsError] = useState(false);
useEffect(() => {
// method
const fetchData = async function () {
// try
try {
// set pending
setIsPending(true);
// response
const response = await fetch(data.url, data);
// handle errors
if (response.status !== 200 && response.status !== 201) {
// throw new error with returned error messages
throw new Error(`Unable to fetch. ${response.statusText}`);
}
// convert to json
const json = await response.json();
// set load data
setLoadData(json);
// set error
setIsError(false);
// set pending
setIsPending(false);
// catch errors
} catch (err) {
// set error
setIsError(`Error fetching data: ${err.message}`);
// set pending
setIsPending(false);
}
};
// invoke fetch data method
fetchData();
}, []);
// return
return {
loadData,
isPending,
isError,
};
};
export default useFetch;
Everytime you change a state in a hook, the component that has the hook in it will rerender, making it call the function again.
So let's start counting the renders/rerenders by the change of state:
Component mounted
setIsPending(true)
setLoadData(json)
setIsPending(false)
(depending if it's successful or not you might get more state changes, and therefore rerenders, and therefore hook being called again)
So 4 is not 8, so why are you getting 8?
I presume you are using React18, and React18 on development and StrictMode will call your useEffect hooks twice on mount: React Hooks: useEffect() is called twice even if an empty array is used as an argument
What can you do to avoid this?
First of all, check on the network tab how many times you are actually fetching the data, I presume is not more than 2.
But even so you probably don't want to fetch the data 2 times, even though this behaviour won't be on production and will only be on development. For this we can use the useEffect cleanup function + a ref.
const hasDataFetched = useRef(false);
useEffect(() => {
// check if data has been fetched
if (!hasDataFetched.current) {
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}
// cleanup function
return () => {
// set has data fetched to true
hasDataFetched.current = true;
};
}, []);
Or as you suggested, we can also add data to the dependency array. Adding a variable to a dependency array means the useEffect will only be triggered again, when the value of the variable inside the dependency array has changed.
(Noting that data is the argument you pass to the useFetch hook and not the actual data you get from the fetch, maybe think about renaming this property to something more clear).
useEffect(() => {
// check if data has been fetched
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}, [data]);
This will make it so, that only if loadData has not been fetched, then it will fetch it. This will make it so that you only have 4 rerenders and 1 fetch.
(There is a good guide on useEffect on the React18 Docs: https://beta.reactjs.org/learn/synchronizing-with-effects)
Every time you change the state within the hook, the parent component that calls the hooks will re-render, which will cause the hook to run again. Now, the empty array in your useEffect dependency should be preventing the logic of the hook from getting called again, but the hook itself will run.

useEffect not being executed in React

This seems to be a common problem but somehow I simply cannot get it to work : I am trying to read some data from a mongoDB database. If I call the NodeJS server directly in a browser, as in
http://localhost:5000/record/nilan
I get the data as a JSON string:
{
"pid":{
"ck":"F19909120:525.522.8788.37",
"name":"nilan",
"cID":"0SL8CT4NP9VO"
}
}
But when I am calling this from a React function RecordDetails(), I get nothing. Please see the code below :
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useParams, useNavigate } from "react-router";
export default function RecordDetails() {
const params = useParams();
const [record1, setRecords] = useState([]);
window.alert('here1');
// This method fetches the records from the database.
useEffect(() => {
async function get1Record() {
//const id = params.id.toString();
const response = await fetch(`http://localhost:5000/record/${params.id.toString()}`);
if (!response.ok) {
const message = `An error occurred: ${response.statusText}`;
window.alert(message);
return;
}
const record1 = await response.json();
const message2 = 'here'
window.alert(message2);
window.alert(record1.pid.name);
window.alert(record1.pid.ck);
setRecords(record1);
}
get1Record();
return;
} , [record1.length]);
window.alert('here2');
// This following section will display the table with the records of individuals.
return (
<div>
<h3>Record Details</h3>
{record1.pid.name}
{record1.pid.ck}
{record1.pid.cID}
</div>
);
}
The process does not seem to be entering the useEffect() part of the code .
When this function RecordDetails() is called, we get the alerts in this sequence. "here1", "here2", "here1", "here2"; then the functions terminates with a blank screen.
I do not get any of the alerts from inside the useEffect, not even the error message alert. What am I doing wrong?
The useEffect hook only runs after the DOM has rendered and when the values inside the dependency array have changed; since you don't change the values inside the dependency array anywhere except within the useEffect hook, it never runs because record1.length will never change -- the code is never reached. You may want to use the useMemo hook instead, which runs once and then will only change when its dependency changes. Also, in order to get it to update, you need to trigger the change from outside the hook, not within, otherwise it will not run. Thus, may I suggest something along the following:
const userInfo = useMemo(() => {
const response = //make server call
return response
}, [dependentValues]);
setRecords({record1: userInfo});
return (
<div onChange={() => {changeDependentValues()}} />
)

React not state not updating first time

So this is my code:
const [module, setModule] = useState([]);
useEffect(()=> {
async function getModuleInfo(){
let ModuleInfo = await firebase
.firestore()
.collection('Modules')
.doc('PBS1Module1')
.get();
if (!ModuleInfo.exists){
console.log('geen module info')
} else {
let ModuleInfov2 = ModuleInfo.data();
setModule(ModuleInfov2)
}} getModuleInfo()
console.log(module)
}, [])
When I go to this screen, the first log is an empty array. Then when I remove the console.log() and save it and than change it back to console.log(module) it gives me the data I need.
What am I doing wrong? All my import statements are good because those are working.
This is because when you are logging out the value module inside the useEffect hook you now have a stale closure. When the component initially renders, the value of module is [] so that is what is passed to the closure inside useEffect. When you update the state via the setModule function, React will rerender the component with the updated state and you get the expected value. To help understand this, try moving your console.log outside of the useEffect. This will make it run every time the component renders, as opposed to now where it only runs on the first render since your dependency array on the useEffect hook is empty.
I tested with the below example and when running it I get logs in the following order:
fresh data is: null
stale data is: null
fresh data is: (4) [1, 2, 3, 4]
import { useEffect, useState } from "react";
const fetchData = async () => {
return [1, 2, 3, 4, ]
}
export const App = () => {
const [data, setData] = useState(null)
useEffect(() => {
const asyncFetch = async () => {
const data = await fetchData();
setData(data);
}
asyncFetch();
console.log("stale data is: ", data);
}, []);
console.log("fresh data is: ", data);
return (
<div>
</div>
)
}

useEffect causing infinite loop or getting errors

I am trying to learn React hooks. I'm creating a simple news app that leverages the NY times api.
When I leave the dependency empty, nothing loads and when I use data as the dependency it goes into an infinite loop.
When I use isLoading, it works but then I receive an error "localhost/:1 Unchecked runtime.lastError: The message port closed before a response was received." and "localhost/:1 Error handling response: TypeError: Cannot read property 'level' of undefined"
main.js
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
console.log(data.results);
};
useEffect(() => {
fetchData();
}, [isLoading]);
return <div className="main">work</div>;
};
export default Main;
I am also receiving a warning, when using isLoading, in the terminal saying "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"
What am I doing wrong?
The infinite loop is caused by the combination of using setData(result) and [data]:
The component mounts and the useEffect is run.
setData(result) will asynchronously update the data value and trigger a rerender.
During the rerender the useEffect will be run again as data will not successfully complete the reference comparison.
Repeat 2 to 3.
The warning "React Hook useEffect has a missing dependency" is self explanatory to an extent.
Making use of an external (to the useEffect) variable that is not included in the dependency array may mean that the value of the variable changes and the useEffect will not be retriggered or that the value may not be the expected value.
Below is a an example of how the original snippet might be fixed:
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
// Create function inside useEffect so that the function is only
// created everytime the useEffect runs and not every render.
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
// setData will update state asynchronously.
// Log the value stored instead.
console.log(result.results);
};
//Run data fetching function.
fetchData();
},
// Both of these are set functions created by useState and will
// not change for the life of the component, but adding them to
// the dependency array will make your linter happy.
// Do not need to check isLoading as it is always true on component
// mount.
[setData, setIsLoading]);
return <div className="main">work</div>;
};
export default Main;
The second argument to useEffect is an array of variables, which trigger the function within useEffect to be called every time they change.
You have [isLoading] as the second argument to useEffect and update the value of this within fetchData(). This is going to cause the useEffect trigger to happen again and again and again.
If you only want to have useEffect call once (in a similar way to ComponentDidMount in class-based components), then you need to specify an empty array as the second argument.
useEffect(() => {
fetchData();
}, []);

How do I correctly implement setState() within useEffect()?

If this useEffect() runs once (using [] as the second parameter), setTicket(response.data) does not update the value of ticket with data. If I run useEffect() with [ticket] as the parameter, it updates the value of ticket with data, but useEffect becomes an infinite loop.
I need it to run once and update the ticket data. I don't think I understand useEffect() and its second parameter.
What do I do to get the expected result?
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
useEffect(() => {
axios
.get("http://localhost:4000/tickets/" + props.match.params.id)
.then((response) => {
setTicket(response.data);
console.log({ ticket });
})
.catch(function (error) {
console.log(error);
});
}, []);
return <div>edit</div>;
};
export default EditTicket;
ticket is a local const. It will never change, and that's not what setTicket is trying to do. The purpose of setTicket is to tell the component to rerender. On that next render, a new local variable will be created, with the new value.
Your code is already written the way it should be written, except that your log statement is not providing you with any useful information. If you want to see that it rerenders with the new value you could move the log statement to the body of the component.
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
console.log('rendering', ticket);
useEffect(() => {
// same as before

Resources