How can I execute conditionally when using useEffect in Reactnative? - reactjs

I created an async function called func in useEffect.
When data is received from firestore, "urlsdata" data is saved with setUrl. After that, if there is data in the url contained in useState, I hide the SplashScreen and change the renderbool to true.
However, when useEffect is executed when the app is first rendered, for some reason it is executed twice, so the api is executed twice in total. I think this is very unnecessary, so the code below is executed only when renderbool is false.
But it still gets executed twice when renderbool is false. How do I make func run only once?
Note that url and renderbool are mandatory.
this is my code
const [url, setUrl] = useState('');
const [renderbool, setRenderbool] = useState(false);
useEffect(() => {
const func = async () => {
if(!renderbool) {
const urlsdata= await storage().ref('/main.jpg').getDownloadURL();
setUrl(urlsdata);
if(url) {
SplashScreen.hide();
setRenderbool(true);
}
}
}
func();
},[url, renderbool]);

Your useEffect is dependent on two variables i.e. url and renderbool in addition to executing on first render, it will execute when ever these variables are re-assigned. Meaning you are listening for changes of url and changing it on the same method.
If you only need to fetch the url once then create a useEffect with [], get the url in this one and have and have another useEffect with [url] which hides the splash screen.
If you don't like this idea then simply create a flag that indicates whether, it has executed, like const [splashHidden, setSplashHidden] = useState(false); then check for this
//EVIL EVIL
if(splashhidden)
{
setSplashHidden(true);
//rest of the code
}
this should force it to only execute once... however the first option is the better way

I think you need to change your second if statement.
When you set a state, you can't immediately check the value of it. The state will be updated after your code done running. So, in your case, the first time your useEffect run (on mount) and when you check
setUrl(urlsdata); <- url state still empty here
if (url) <- this will return false, which leaves your `renderbool` state still `false`.
But, because your url state is now changed after the code is done, the useEffect will be called again for the second time. So, what you can do is to check your urlsdata instead
if (urlsdata) {
SplashScreen.hide();
setRenderbool(true);
}

Related

How to perform some actions when the state changes, but only for the first update?

I have a state which initially is undefined. After certain actions have been done, I am updating that state and I need to run something as soon as that state is updated. But, since, that state changes quite often, I don't want to use useEffect cuz, I don't want the code to be executed everytime state changes. Just for the first update. How can I achieve that? Here is the code demonstartion
const [annotations,setAnnotations] =useState()
// A bunch of changes and after some time, called setAnnotations(value)
useEffect(()=> {
// here I need to do something. But, don't have the latest annotations.
// I only need to do // something only for the first time
},[])
I don't want the code to be executed everytime state changes. Just for the first update
You could for example set a custom flag to check whether it already called or not yet. And depend on it.
const flag = useRef<boolean>(true);
useEffect(() => {
if (flag.current) {
// do stuff
flag.current = false; // set flag to false so it won't be called anymore
}
}, []);
I would do the same as kind user, but add your state as a dependency of the useEffect. That way it only runs when the state changes in the first place. Just makes the code a little more robust.
const [annotations,setAnnotations] =useState()
const flag = useRef<boolean>(true);
useEffect(() => {
if (flag.current) {
// do stuff
flag.current = false; // set flag to false so it won't be called anymore
}
}, [annotations]);

useEffect dependency array understanding

Hello I am learning about the useEffect hook in react, and I need some clarification. It is my understand that when we provide an empty dependency array that the code inside of the useEffect hook will only run once during the application's initial mount. I need some helping understand why the code below runs every time I refresh the page even though I provided an empty dependency array?
Thank you
const [numberOfVistors, setnumberOfVistors] = useState(() => {
localStorage.getItem("numberOfVistorsKey")
});
useEffect (() => {
let vistorCount = localStorage.getItem("numberOfVistorsKey")
if (vistorCount > 0)
{
vistorCount = Number(vistorCount) + 1
localStorage.setItem("numberOfVistorsKey", vistorCount)
setnumberOfVistors(vistorCount)
}
else
{
vistorCount = 1
setnumberOfVistors(vistorCount)
localStorage.setItem("numberOfVistorsKey", vistorCount)
}
}, [])
The context of an application does not persist across multiple pageloads or refreshes except through the few ways that allow for persistent data - such as local storage, cookies, and an API with a server somewhere. For the same reason, doing
let num = 5;
button.onclick = () => { num = 10; }
results in num still being 5 after you click and reload the page - because reloading the page starts the script from the very beginning again, on the new page.
If you want to keep track of whether that particular section of code has ever run before, use a flag in storage, eg:
useEffect(() => {
if (localStorage.hasVisitedBefore) return;
localStorage.hasVisitedBefore = 'yes';
// rest of effect hook
, []);
I need some helping understand why the code below runs every time I refresh the page even though I provided an empty dependency array?
Every time you refresh the page, the React component mounts for the first time.
useEffect(..., []) was supplied with an empty array as the dependencies argument. When configured in such a way, the useEffect() executes the callback just once, after initial mounting.
Try going through it !!
Whenever you refresh the page, React component will be re-rendered. Therefore useEffect() is called everytime.
In order to avoid it, you have to do like this.
const [numberOfVistors, setnumberOfVistors] = useState(() => {
localStorage.getItem("numberOfVistorsKey")
});
useEffect (() => {
let vistorCount = localStorage.getItem("numberOfVistorsKey")
if (vistorCount > 0)
{
vistorCount = Number(vistorCount) + 1
localStorage.setItem("numberOfVistorsKey", vistorCount)
setnumberOfVistors(vistorCount)
}
else
{
vistorCount = 1
setnumberOfVistors(vistorCount)
localStorage.setItem("numberOfVistorsKey", vistorCount)
}
}, [numberOfVistors])
This code will render your component whenever numberOfVistors are changed.

useEffect infinite loop when getting data from database

In my project, for sending a request for getting my user data and show them. I wrote the above code but i realised that if i pass the "people" to useEffect's dependency (second parameter) react sends infinite request to my firebase but if i delete and keep the second parameter empty the useEffect works correct what is the difference between these two?
Here is the code that goes to infinite loop:
const [people, setPeople]=useState([])
useEffect(() => {
const unsubscribe=database.collection("people").onSnapshot(snapshot=>
setPeople(snapshot.docs.map(doc=>doc.data()))
)
return () => {
unsubscribe()
}
}, [people]) // if i change the second parameter with an empty list this problem solved.
return (
<div>
<h1>TinderCards</h1>
<div className="tinderCards_cardContainer">
{people.map(person =>
<TinderCard
className="swipe"
key={person.name}
preventSwipe={["up","down"]}
>
<div style={{backgroundImage: `url(${person.url})`}} className="card">
<h3>{person.name}</h3>
</div>
</TinderCard>
)}
</div>
</div>
)
Essentially, the useEffect hook runs the inner function code every time any of the dependencies in the dependency array (second parameter) change.
Since setPeople changes people, the effect keeps running in an infinite loop:
useEffect(() => {
... setPeople() ... // <- people changed
}, [people]); // <- run every time people changes
If you needed somehow the value of people and you need to have it in the dependency array, one way to check is if people is not defined:
useEffect(() => {
if (!people) {
// ... do something
setPeople(something);
}
}, [people]);
As you correctly pointed out, simply taking off the people dependency tells the effect to only run once, when the component is "mounted".
On an extra note, you may be wondering why people is changing if you are fetching the same exact results. This is because the comparison is shallow, and every time an array is created, it's a different object:
const a = [1,2,3];
const b = [1,2,3];
console.log(a === b); // <- false
You would need to do deep equality checks for that.
The issue is after you set state in useEffect, the people value will be changed which will trigger another useEffect call hence an infinite loop.
You can modify it to this:-
useEffect(() => {
const unsubscribe=database.collection("people").onSnapshot(snapshot=>
setPeople(snapshot.docs.map(doc=>doc.data()))
)
return () => {
unsubscribe()
}
}, [])
PROBLEM
useEffect runs every time when any one of values given to dependency array changes. Since, you're updating your people after the db call. The reference to array people changes, hence triggering an infinite loop on useEffect
SOLUTION
You do not need to put people in the dependency array.
Your useEffect function doesn't depend on people.
useEffect(() => {
const unsubscribe=database.collection("people").onSnapshot(snapshot=>
setPeople(snapshot.docs.map(doc=>doc.data()))
)
return () => {
unsubscribe()
}
}, [])
main problem is that the people array which is being created at every set-call is not the same. The object is completely different.
I also had this trouble as i want to display the contents as soon as the some new "people" is added to the database from the admin panel, but it turns out that without refreshing this thing cannot be solved otherwise u can make your own hook with PROPER comparisons .
Maybe u can try by comparing the length of the PEOPLE array. I haven't tried it yet but i think it will work.

React Hooks API call - does it have to be inside useEffect?

I'm learning React (with hooks) and wanted to ask if every single API call we make has to be inside the useEffect hook?
In my test app I have a working pattern that goes like this: I set the state, then after a button click I run a function that sends a get request to my API and in the .then block appends the received data to the state.
I also have a useEffect hook that runs only when the said state changes (using a dependency array with the state value) and it sets ANOTHER piece of state using the new data in the previous state. That second piece of state is what my app renders in the render block.
This way my data fetching actually takes place in a function run on a button click and not in the useEffect itself. It seems to be working.
Is this a valid pattern? Thanks in advance!
Edit: example, this is the function run on the click of the button
const addClock = timezone => {
let duplicate = false;
selectedTimezones.forEach(item => {
if (item.timezone === timezone) {
alert("Timezone already selected");
duplicate = true;
return;
}
});
if (duplicate) {
return;
}
let currentURL = `http://worldtimeapi.org/api/timezone/${timezone}`;
fetch(currentURL)
.then(blob=>blob.json())
.then(data => {
setSelectedTimezones(prevState => [...prevState, data]);
}
);
}
Yes, apis calls that happen on an action like button click will not be part of useEffect call. It will be part of your event handler function.
When you call useEffect, you’re telling React to run your “effect”
function after flushing changes to the DOM
useEffect contains logic which we would like to run after React has updated the DOM. So, by default useEffect runs both after the first render and after every update.
Note: You should always write async logic inside useEffect if it is not invoked by an event handler function.
Yes, you can make api requests in an event handler such as onClick.
What you don't want to do is make a request directly inside your functional component (since it will run on every render). As long as the request is inside another function and you only call that function when you actually want to make a request, there is no problem.

How does React Hooks useCallback "freezes" the closure?

I'd like to know how does React "freezes" the closure while using the useCallback hook (and with others as well), and then only updates variables used inside the hook when you pass them into the inputs parameter.
I understand that the "freeze" may not be very clear, so I created a REPL.it that shows what I mean: https://repl.it/repls/RudeMintcreamShoutcast. Once you open the code, open your web browser console and start clicking on the count button.
How come the value outside compared to the one inside, for the same variable, is different, if they're under the same closure and referencing the same thing? I'm not familiar with React codebase and so I suppose I'm missing an under the hood implementation detail here, but I tried to think how that could work for several minutes but couldn't come up with a good understanding on how React is achieving that.
The first time the component is rendered, the useCallback hook will take the function that is passed as its argument and stores it behind the scenes. When you call the callback, it will call your function. So far, so good.
The second time that the component is rendered, the useCallback hook will check the dependencies you passed in. If they have not changed, the function you pass in is totally ignored! When you call the callback, it will call the function you passed in on the first render, which still references the same values from that point in time. This has nothing to do with the values you passed in as dependencies - it's just normal JavaScript closures!
When the dependencies change, the useCallback hook will take the function you pass in and replace the function it has stored. When you call the callback, it will call the new version of the function.
So in other words, there's no "frozen"/conditionally updated variables - it's just storing a function and then re-using it, nothing more fancy than that :)
EDIT: Here's an example that demonstrates what's going on in pure JavaScript:
// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};
function useState(init) {
if (storage.data === undefined) {
storage.data = init;
}
return [storage.data, (value) => storage.data = value];
}
function useCallback(fn) {
// The real version would check dependencies here, but since our callback
// should only update on the first render, this will suffice.
if (storage.callback === undefined) {
storage.callback = fn;
}
return storage.callback;
}
function MyComponent() {
const [data, setData] = useState(0);
const callback = useCallback(() => data);
// Rather than outputting DOM, we'll just log.
console.log("data:", data);
console.log("callback:", callback());
return {
increase: () => setData(data + 1)
}
}
let instance = MyComponent(); // Let's 'render' our component...
instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();
instance.increase(); // and again...
instance = MyComponent();
I came here with a similar, rather vague uncertainty about the way useCallback works, its interaction with closures, and the way they are "frozen" by it. I'd like to expand a bit on the accepted answer by proposing to look at the following setup, which shows the working of useCallback (the important aspect is to ignore the linter's warning, for pedagogical reasons):
function App() {
const [a, setA] = useState(0)
const incrementWithUseCallback = useCallback(() => {
// As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
console.log(a)
setA(a + 1)
}, []) // but.. the linter should complain about this, saying that `a` should be included!
const incrementWithoutUseCallback = () => {
// This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
console.log(a)
setA(a + 1)
}
return (
<div>
<button onClick={incrementWithUseCallback}>Increment with useCallback</button>
<button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
</div>
)
}
So we clearly see that useCallback effectively "freezes" its closure at a certain moment in time, which is a concept that must be understood clearly, in order to avoid confusing problems, which are sometimes also referred as "stale closures". This article probably does a better job of explaining it than me: https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures
Here's a slightly another view on example code provided by Joe Clay, which emphasizes closure context in which callback is called.
//internal store for states and callbacks
let Store = { data: "+", callback: null };
function functionalComponent(uniqClosureName) {
const data = Store.data;//save value from store to closure variable
const callback = Store.callback = Store.callback || (() => {
console.log('Callback executed in ' + uniqClosureName + ' context');
return data;
});
console.log("data:", data, "callback():", callback());
return {
increase: () => Store.data = Store.data + "+"
}
}
let instance = functionalComponent('First render');
instance.increase();
instance = functionalComponent('Second render');
instance.increase();
instance = functionalComponent('Third render');
As you see, callback without dependencies will be always executed in the closure where it was memorized by useCallback, thus 'freezing' closure.
It happens because when function for callback is created, it is created only once, during first 'render'. Later this function is re-used, and use value of data which was recorded from Store.data during first call.
In the next example you can see the closure 'freezing' logic "in essence".
let globalX = 1;
const f = (() => {
let localX = globalX; return () => console.log(localX); }
)();
globalX = 2;//does not affect localX, it is already saved in the closure
f();//prints 1

Resources