setInterval inside useEffect hook - reactjs

First, I will say that I am quite new to react.
I am fetching data from firebase and randomly changing the data on the screen in setInterval. There are some things going on that I do not understand, so I would like to ask about them.
I have doubts about the working principle of the interval and useEffect. Inside the useEffect there is no dependency so useEffect executes only for the first time. How does function inside the interval works after every some second, when useEffect only works for once? I think interval function is inside the useEffect,useEffect should execute in order to run interval function.
When I set the time to 1000 inside setInterval , react sayscurrent_quote state is undefined, but everything is fine when I set it to 2000 or over. Why?
Return callback function of useEffect . I know it runs everytime right before the next render, to clean the interval of the previous render,but when I console something inside return why it does not execute?
import db from '../components/db'
import {useState,useEffect} from 'react'
const Home = ()=>{
const [current_quote,set_current_quote] = useState({});
const [isloaded, set_loaded]= useState(false)
let arr_data=[];
useEffect(()=>{
// fetching data from database
db.collection("data").get().then((querySnapshot) => {
querySnapshot.forEach(element => {
var data = element.data();
arr_data.push(data);
})
})
const interval = setInterval(()=>{ // randomly changing state
set_current_quote(arr_data[Math.floor(Math.random()*(arr_data.length))])
set_loaded(true);
},1000)
return ()=>{
clearInterval(interval)
}
},[])
return (
<div>
<h1>{ isloaded && current_quote.Quote} </h1> // displaying random data
<h4>{ isloaded && current_quote.Author}</h4>
</div>
)}
export default Home;

Hope this moves your understanding forward:
useEffect runs once, reads from the database once, and sets the interval function once. The javascript engine will call the interval function you set ever interval milliseconds until you clearInterval() it. The code is OK. Inside the interval method, you're calling a method set_current_quote() that you received from React's useState() hook. That will dirty the component and cause it to re-render.
the 1st time your code runs, current_quote = {}, so current_quote.Quote is undefined. However, while current_quote is undefined, isloaded should be false - so at a glance I'm not sure why you're getting the undefined error.
The useEffect return callback is called when the component is torn down. If you remove the compoent from the dom (e.g. by navigating away) you'll see it gets called.
You can move arr_data to inside useEffect. It's not used outside that scope and it's not saved across renders (no const [arr, setArr] = useState()).
// instead of this...
let arr_data = []
querySnapshot.forEach(element => {
var data = element.data();
arr_data.push(data);
})
// you can do this...
let arr_data = querySnapshot.map(element => element.data())

Related

Returning a function in useEffect

I'm trying to understand why returning a function inside useEffect hook executes during the second time. For example:
const App = (props) => {
const [change, setChange] = useState(false);
useEffect(() => {
return () => console.log("CHANGED!");
}, [change]);
return (
<div>
<button onClick={() => setChange(true)}>CLICK</button>
</div>
);
};
The first time the component renders, 'CHANGED' is not being logged, but after I click the button, it does. Does anyone know why?
Per the React docs - If your effect returns a function, React will run it when it is time to clean up. https://reactjs.org/docs/hooks-effect.html. The returned function will fire when the component unmounts.
If you use console.log() inside of useEffect but not in the return block, then it will be executed AFTER rendering. Putting it into return block means, you want use it as a clean-up function, so it'll be executed before the component unmounts or right before the next scheduled effect's execution. However, in your case, for the very first time, you neither have a scheduled effect nor something mounted. That's why you see the string "CHANGED" only when you click on the button.

useCallback vs useEffect in React

What's the different between useEffect when you pass it dependencies as the second parameter and useCallback?
Don't both essentially run the function/code passed as the first parameter whenever the dependencies passed as the second parameter change?
From what I've read the two hooks are intended to serve different purposes, but my question is whether they in actuality could be used interchangeably because they functionally do the same thing
They're too different.
useEffect will run the function inside when the dependency array changes.
useCallback will create a new function when the dependency array changes.
You can't switch useEffect with useCallback alone because you also need the logic to run the newly created function. (I suppose you could implement this if you used a ref as well, but that'd be quite strange.)
You can't switch useCallback with useEffect because you very often don't want to run the newly created function immediately - rather, you usually want to pass it as a prop to some other component.
useCallback primarily exists for optimization purposes, to reduce re-renders of a child component.
No, They are not same.
useEffect - is used to run side effects in the component when something changes. useEffect does
not return you anything. It just runs a piece of code in the component.
useCallback - Whereas useCallback returns a function, it does not execute the code actually. It is important to understand that
functions are objects in Javascript. If you don't use useCallback, the function you define inside the component is
re-created whenever the component rebuilds.
Example
Consider this example, this component will go in a infinite loop. Think Why?
const TestComponent = props => {
const testFunction = () => {
// does something.
};
useEffect(() => {
testFunction();
// The effect calls testFunction, hence it should declare it as a dependency
// Otherwise, if something about testFunction changes (e.g. the data it uses), the effect would run the outdated version of testFunction
}, [testFunction]);
};
Because on each render the testFunction
would be re-created and we already know that ueEffect will run the code when ever the testFunction changes. And since testFunction changes on each render, the useEffect will keep on running, and hence an infinite loop.
To fix this, we have to tell react, hey please don't re-create the testFunction on each render, create it only on first render (or when something changes on which it depends).
const TestComponent = props => {
const testFunction = useCallback(() => {
// does something.
}, []);
useEffect(() => {
testFunction();
// The effect calls testFunction, hence it should declare it as a dependency
// Otherwise, if something about testFunction changes (e.g. the data it uses), the effect would run the outdated version of testFunction
}, [testFunction]);
};
This won't be a infinite loop, since instance of testFunction will change only on first render and hence useEffect will run only once.
useEffect will run the function inside when the dependency array changes.
useCallback will create a new function when the dependency array changes.
Let's take an example, If I run the below code and click the first button it'll always rerender MemoComponent as well. Why because every time
we are passing new onClick function to this. To avoid re-rendering of MemoComponent what we can do is wrap onClick to useCallback. Whenever you want to create a new function pass state to the dependence array.
If you want to perform some action on state change you can write inside useEffect.
const Button = ({ onClick }) => {
console.log("Render");
return <button onClick={onClick}>Click</button>;
};
const MemoComponent = React.memo(Button);
export default function Home() {
const [state, setState] = useState(1);
useEffect(() => {
console.log(state); // this will execute when state changes
}, [state]);
const onClick = () => {};
// const onClick = useCallback(() => {},[])
return (
<main>
<button onClick={() => setState(1 + state)}>{state}</button>
<MemoComponent onClick={onClick} />
</main>
);
}
useEffect
It's the alternative for the class component lifecycle methods componentDidMount, componentWillUnmount, componentDidUpdate, etc. You can also use it to create a side effect when dependencies change, i.e. "If some variable changes, do this".
Whenever you have some logic that is executed as reaction to a state change or before a change is about to happen.
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, [state]);
OR
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, []);
useCallback
On every render, everything that's inside a functional component will run again. If a child component has a dependency on a function from the parent component, the child will re-render every time the parent re-renders even if that function "doesn't change" (the reference changes, but what the function does won't).
It's used for optimization by avoiding unnecessary renders from the child, making the function change the reference only when dependencies change. You should use it when a function is a dependency of a side effect e.g. useEffect.
Whenever you have a function that is depending on certain states. This hook is for performance optimization and prevents a function inside your component to be reassigned unless the depending state is changed.
const myFunction = useCallback(() => {
// execute your logic for myFunction
}, [state]);
Without useCallback, myFunction will be reassigned on every render. Therefore it uses more compute time as it would with useCallback.

React setInterval State not changed / updated

Maybe I'm missing something, but I can't find a solution for that.
I have a React component and want to run some periodic background job (setInterval)
This job use some information of the components state and should also be able to change that.
My component:
export default function Form() {
const [state, setState] = useState<IState>(initialState);
useEffect(() => {
//init component and other stuff
// trigger background Task
setInterval(backgroundJob, 10000);
}, []);
function backgroundJob(){
state.xxx
}
function handleButtonClick(){
state.xxx = state.xxx + 1;
setState({
...state,
});
}
}
The main problem is, the backgroundJob Function is able to access the state, but it's only the initalState. If the state is updated, (e.g. on a button click via the handleButtonClick() function), the next Interval tick still has old values in the state
Do I misunderstood some basic concept here?
You need to add backgroundJob to you dependency list, but also you need to clear this interval when the effect is re-run:
Note that in the way your code is written this will happen every time the component renders. You can use useCallback to optimize if needed.
useEffect(() => {
//init component and other stuff
// trigger background Task
const intervalId = setInterval(backgroundJob, 10000);
return () => clearInterval(intervalId)
}, [backgroundJob]);
In not doing so, the backgroundJob you are running is the one from the first render which only "see"s (via its closure) the initial state (AKA Stale Closure)

React JS | using setTimeout prints a number on Page

I am simply trying to change the heading and content states using React Hooks but I get a number shown on the page, a little google search showed up a bunch of stuff related to how setInterval and Timeout generate a key or value but I have no idea why they're showing up on the page.I can hide it using an empty div but I am curious if I am doing anything wrong, also if I use a class instead of a function the value rapidly increases and my CPU maxes out.
function MyComponent (){
const [heading, setHeading] = useState('React(Loading)')
const [content, setContent] = useState('Loading...')
return(
<main>
<h1>{heading}</h1>
<p>{content}</p>
{
setTimeout(() =>{
setHeading('React(Loaded)')
setContent('Loaded')
}, 2000)} // should change the values, which it does with addition of a number
</main>
);
}
The resulting page is that renders is here
Also on a side note I tried using a onload function to do the same thing but nothing happens.
setTimeout returns a number, which is used to identify the timeout when you use clearTimeout. That is why you see the number below the content.
To hide the number, you should move the setTimeout to be outside of the return function. Also, you should use as little JS as possible in the return statement and just use JSX over there, to make the component more clear and readable.
But just moving the setTimeout to be before the return statement is not enough. The function will run on every render, and there are many things that can trigger a re-render - a state change, or a parent re-rendering. So on every re-render, you will set a new timeout. The timeout itself updates a state which triggers a render which triggers the setTimeout - so you are creating an infinite loop.
So you want to call setTimeout only once - you can use useEffect, which will re-run only when the dependency array changes, but if you will leave it empty, it will run only once, because nothing will change and a re-run will never be triggered.
function MyComponent (){
const [heading, setHeading] = useState('React(Loading)')
const [content, setContent] = useState('Loading...')
useEffect((
setTimeout(() =>{
setHeading('React(Loaded)')
setContent('Loaded')
}, 2000)
), []);
return(
<main>
<h1>{heading}</h1>
<p>{content}</p>
</main>
);
}
So, by using the above answer we get the following error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function
The reason for this is again the fact that setTimeout returns a number, the final answer is to use the code as a separate function as below:
useEffect( timeOutFunction, [])
function timeOutFunction() {
setTimeout(() => {
setHeading('React(Loaded)')
setContent('Loaded'), 2000)
}

React useState and useEffect nesting. Is this okay?

I'm playing with React hooks, and I'm trying to make a very basic clock. It's working fine and dandy, even the profiler says there are no weird rerenders going on. The part that worries me a bit is where useEffect calls updateTimeAndDate, which changes the state. I feel like it should go into a rerender loop. Why does it still work? I can't put it any better, sorry. :D
Could it also be a bit nicer? Here it is:
const addZero = (trunk) => (trunk >= 10 ? "" : "0") + trunk;
const [hours, setHours] = useState(addZero(0));
const [minutes, setMinutes] = useState(addZero(0));
let timeAndDate;
function updateTimeAndDate() {
timeAndDate = new Date();
setHours(addZero(timeAndDate.getHours()));
setMinutes(addZero(timeAndDate.getMinutes()));
}
useEffect(() => {
updateTimeAndDate();
});
setInterval(updateTimeAndDate, 500);
I put useEffect there to update the time immediately after loading the page, instead of waiting for a long long half second.
There are a couple issues that you will encounter with the code you've showed:
You will run an infinitely loop that causes your component to rapidly re-render. This is because you are calling setInterval on every render, and by calling updateTimeAndDate within setInterval, you are updating state, which in turns causes the component to re-render.
You have not specified a dependency array in your useEffect, so it will run on every re-render, further amplifying the infinite loop problem.
A possible alternative would be to only call the useEffect once by specifying an empty dependency array. You can also clear the interval when your component unmounts (by specifying a return value in your useEffect).
Here's a codesandbox demonstrating: https://codesandbox.io/s/stack-overflow-set-interval-89spq
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const addZero = trunk => (trunk >= 10 ? "" : "0") + trunk;
const [hours, setHours] = useState(addZero(0));
const [minutes, setMinutes] = useState(addZero(0));
const [seconds, setSeconds] = useState(addZero(0));
let timeAndDate;
function updateTimeAndDate() {
timeAndDate = new Date();
setHours(addZero(timeAndDate.getHours()));
setMinutes(addZero(timeAndDate.getMinutes()));
setSeconds(addZero(timeAndDate.getSeconds()));
}
useEffect(() => {
const interval = setInterval(updateTimeAndDate, 1000);
return () => clearInterval(interval);
}, []);
// setInterval(updateTimeAndDate, 500);
return (
<div className="App">
<h1>{hours}</h1>
<h2>{minutes}</h2>
<h3>{seconds}</h3>
</div>
);
}
To answer your questions
Is it ok?
Calling updateTimeAndDate (which updates the state) inside useEffect is ok. Nevertheless, in your current code you will face issues with interval as #RobertCooper mentioned.
However, I disagree with Robert on one thing. And it is that you will fall in the infinite loop. This leads us to your second question:
I feel like it should go into a rerender loop. Why does it still work?
Your current code does not fall into infinite loop due to the way React handles effects.
According to the documentation:
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects.
Therefore, as you update the state with same hour and same minute React does not fire your effect. Hence, there is no infinite loop in this particular case.

Resources