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)
}
Related
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.
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())
I've just started learning React and was putting together a small app which makes calls to a quotes API. The API has an endpoint that returns a random quote. When the app initially loads it makes a call to the API and shows a quote, and there's a button that can be clicked to get a new random quote (new call to the API).
I have a root component named App. This component has a QuoteWrap component as a child. The QuoteWrap component has two children: the button that is used to get a new random quote and a Quote component which shows the author of the quote and the quote itself. This is the code inside of the QuoteWrap component:
export default function QuoteWrap() {
const { quoteData, isLoading, fireNewCall } = useQuote();
const handleClick = () => {
fireNewCall();
};
return(
<>
<button onClick={handleClick}>Get random quote</button>
{ isLoading ?
<h2>Loading...</h2>
:
<Quote author={quoteData.author} quote={quoteData.quote} />
}
</>
);
}
useQuote() is a custom hook that manages the calls to the API and returns 3 values: 1- the data, 2- if a call is in process and 3- a function to make a call to the API.
Obviously, every time the button is clicked, the whole QuoteWrap component is re-rendered (as quoteData and isLoading change). But really, the button doesn't need to be re-rendered as it never changes.
So I thought: ok, I can move the button up to the App component. But then I don't have access to the fireNewCall function in the useQuote hook.
How can I prevent the button from being re-rendered? Is it even important in this case or am I getting too obsessed with React re-renders?
Thanks!
Your component will re-render every time the handleClick function changes, which is every time that QuoteWrap is rendered.
The solution is the useCallback hook. useCallback will return the same function to handleClick, every time QuoteWrap is rendered, so long as the dependencies haven't changed.
https://reactjs.org/docs/hooks-reference.html#usecallback
You would use it like this:
const handleClick = useCallback(() => {
fireNewCall();
},[fireNewCall]);
fireNewCall is the dependency, so as long as useQuote returns a stable fireNewCall function, then your button will not re-render, since the handleClick property hasn't changed.
I think you might get too obsessed with React re-renders. The button should be re-rendered because, handleClick should be changed when fireNewCall changed for some case. Even if, handleClick will never be changed. It's no need to think about an element re-render.
Pretty much what Benjamin and Viet said - in your original code, a new function is assigned to handleClick on each render. You can use React.useCallback to maintain the original function reference and only update it when something in the dependency array changes - in this case, just fireNewCall needs to go into the dependency array.
But as Viet says, don't get too obsessed with it. Using React.useCallback might even slow down your code. Check out Kent C. Dodds When to useMemo and useCallback post for more insight.
I'm somewhat new to react, and I'm trying to learn when every time a component will re-render. Usually I'm good at figuring this stuff out on my own, but I can't seem to find the logic in these
Exhibit A
const Science = () => {
console.log('Rendering')
useEffect(() => console.log('Effect ran'))
console.log('Returning')
return <div></div>
}
This outputs
Rendering
Returning
Rendering
Returning
Effect ran
I don't understand why it re-renders, and why the useEffect code comes after both renders and returns.
Exhibit B
const Science = () => {
console.log('Rendering')
useEffect(() => console.log('First effect ran'))
useEffect(() => console.log('Second effect ran'))
console.log('Returning')
return <div></div>
}
This outputs
Rendering
Returning
Rendering
Returning
First effect ran
Second effect ran
My guess to why it re-renders in Exhibit A was that useEffect causes a re-render, but seen here, only one useEffect will cause a re-render. Unsure of this behavior.
Exhibit C
const Science = () => {
console.log('Rendering')
let numRenders = useRef(0)
console.log(++numRenders.current)
console.log('Returning')
return <div></div>
}
This one really confuses me, as it outputs
Rendering
1
Returning
Rendering
1
Returning
I don't understand why on the second render, it doesn't increment. I know that it does permanently increment the value, and not just for the logging.
Thanks.
At first, your jsx is rendered. Then lifecycle hooks executed and the component is rerendered. then each time that any state changes the component is rendered or props are changed.
Your useEffect code doesn't have any dependencies but you did not mention it. This way is correct in this case
useEffect(() => console.log("useEffect was ran") , [])
useEffect callback executed asynchronously after the render phase, that's exactly A and B.
As for Exhibit C, it outputs:
Rendering
1
Returning
And not rendered twice, it renders twice because of other reasons (like a render of a parent which you not showing us). Anyways, useRef doesn't trigger render so due to closures updating the reference is pointless (it will always show the first value rendered).
In other words, as it's function component, render triggers the function and the updated value will be valuated.
See codesandbox attached.
I have a hook to save scroll value on window scroll.
(BTW, my state object is more complicated than this example code. That's why I'm using useReducer. I simplified the state for this question.)
Then when I attached that hook to App, everything inside useEffect runs every time I scroll.
I get that console.log(scroll) prints every time on scroll, but why is console.log("somethingelse") also prints every time on scroll?
I have other codes to put inside useEffect, so it's a huge problem if everything inside useEffect runs on scroll.
How do I make only useScroll() related code to run on scroll, and everything else runs only on re-render like useEffect supposed to do?
I made CodeSandbox example below.
function App() {
const scroll = useScroll();
useEffect(() => {
console.log(scroll);
console.log(
"this also runs everytime I scroll. How do I make this run only on re-render?"
);
});
return ( ... );
}
The way your code is currently written the useEffect code will be executed on each update. When scrolling the state is updated, so the effect will be executed.
You can pass props as the second variable to the useEffect to determine whether it should run or not.
So, if you create an effect like this it will run every time the scroll is updated
useEffect(() => {
console.log('This wil run on mount when scroll changes')
}, [scroll]) // Depend on scroll value, might have to be memoized depending on the exact content of the variable.
useEffect(() => {
console.log('This will only run once when the component is mounted')
}, []) // No dependencies.
useEffect(() => {
console.log('This will run on mount and when `someStateVar` is updated')
}, [someStateVar]) // Dependent on someStateVar, you can pass in multiple variables if you want.
Without passing the second argument, the effect will always run on every render. You can add multiple effects in a single component. So, you'll probably want to write one containing the stuff that needs to be executed when scroll updates and create one or more others that only run on mount of after some variable changes.
There is a concept of cleanup functions which can be attached to an useEffect hook. This cleanup function will be run once the rerender is complete. It's usually used in scenarios where you need to unsubscribe from a subscription once the render is complete. Below is the sample code from React website
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});
More details can be found in the blogpost
useEffect Hook