React component reacts unexpectedly - javascript

The following is a simple react component:
import React from "react";
import { useState } from "react";
export default function Comp() {
let [count, setCount] = useState(1);
function countUp(){
setCount(count + 1);
}
setInterval(countUp, 1000);
return <h2>{count}</h2>
}
I expected the counter to go up every second
But for some reason, after ten - twenty seconds something starts to go wrong
See here:
https://stackblitz.com/edit/react-az7qgn?file=src/comp.jsx
Can anyone explain this?

You should use useEffect hook to set up that properly. I can provide an example.
import React, { useState, useEffect } from "react";
export default function Comp() {
const [count, setCount] = useState(1);
useEffect(() => {
const interval = setInterval(() => {
setCount(state => state + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <h2>{count}</h2>
}
A couple of notes.
In general, you would prefer const over let, but this is mandatory when destructuring things coming from React.
I suggest to read Using the Effect Hook on React docs to more information about useEffect.
Basically, useEffect allows you to achieve similar results to componentDidMount and componentDidUpdate lifecycle methods for class components. Also, in this specific case, by returning a function in useEffect callback, we make sure to clear the scheduled callback when it's time to clean up, which means after each run. This actually avoids the mess of stacking many setInterval on top of each other.
Also, when you setCount it's preferable to get the previous state by using the callback form, because that will be always up-to-date.

When calling setInterval(), it returns an interval id. Your code is not saving the variable, and thus you cannot reset it. On smaller iterations, you will not see the changes for every iteration. But, as the number of times that setInterval() is called increases from 0 to N, more timers are being initiated, and you will rapidly see flashes of numbers as they increase, because every interval is changing the state of count.
In other words, you are creating more and more timers as time goes on, rather than creating timers for one-time use. You will need to call clearInterval(timer_id_goes_here) to clear the timer. See code examples in the link below.
https://www.w3schools.com/jsref/met_win_setinterval.asp

Related

For some reason I am getting 4 responses when I should be getting one using axios? [duplicate]

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log("rendered", count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
useEffect being called twice on mount is normal since React 18 when you are in development with StrictMode. Here is an overview of what they say in the documentation:
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
This only applies to development mode, production behavior is unchanged.
It seems weird, but in the end, it's so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
export default Counter;
In this very detailed article called Synchronizing with Effects, React team explains useEffect as never before and says about an example:
This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.
For your specific use case, you can leave it as it's without any concern. And you shouldn't try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the documentation:
React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.
In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned... So even though there is an extra request, it won’t affect the state thanks to the abort.
In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
function TodoList() {
const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
// ...
Update: Looking back at this post, slightly wiser, please do not do this.
Use a ref or make a custom hook without one.
import type { DependencyList, EffectCallback } from 'react';
import { useEffect } from 'react';
const useClassicEffect = import.meta.env.PROD
? useEffect
: (effect: EffectCallback, deps?: DependencyList) => {
useEffect(() => {
let subscribed = true;
let unsub: void | (() => void);
queueMicrotask(() => {
if (subscribed) {
unsub = effect();
}
});
return () => {
subscribed = false;
unsub?.();
};
}, deps);
};
export default useClassicEffect;

Why useEffect running twice and how to handle it well in React?

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log("rendered", count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
useEffect being called twice on mount is normal since React 18 when you are in development with StrictMode. Here is an overview of what they say in the documentation:
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
This only applies to development mode, production behavior is unchanged.
It seems weird, but in the end, it's so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
export default Counter;
In this very detailed article called Synchronizing with Effects, React team explains useEffect as never before and says about an example:
This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.
For your specific use case, you can leave it as it's without any concern. And you shouldn't try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the documentation:
React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.
In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned... So even though there is an extra request, it won’t affect the state thanks to the abort.
In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
function TodoList() {
const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
// ...
Update: Looking back at this post, slightly wiser, please do not do this.
Use a ref or make a custom hook without one.
import type { DependencyList, EffectCallback } from 'react';
import { useEffect } from 'react';
const useClassicEffect = import.meta.env.PROD
? useEffect
: (effect: EffectCallback, deps?: DependencyList) => {
useEffect(() => {
let subscribed = true;
let unsub: void | (() => void);
queueMicrotask(() => {
if (subscribed) {
unsub = effect();
}
});
return () => {
subscribed = false;
unsub?.();
};
}, deps);
};
export default useClassicEffect;

Triggering useEffect only certain conditions

I have basic understanding of useEffect. Without second parameter (dependency array) it runs on every render. With empty array, it runs on first render. With parameters in array, it runs whenever some of parameters changes.
Say I have useEffect with two dependencies (from GraphQL query): result.data and result.loading. I want useEffect to run if result.data changes, and result.loading is false. Purpose is for example to update Redux store:
useEffect(() => {
if (result.loading) return;
dispatch(updatePhotos([...photos, ...result.data.photos]));
}, [result.data, result.loading]);
But there's a catch: I have to include photos to list of dependencies. However, photos variable will be updated in other place, and it triggers this useEffect again.
How can I run useEffect only when those two variables changes?
I can of course use useState to store variable resultFetched, set it to true in useEffect and then dispatch only if it is false. But at some point I have to change it back to true, and useEffect runs again, since I can't manually change result.data or result.loading.
I'm lost how to properly use useEffect in these situations when there is lots of variables to handle.
Currently I'm building infinite scrolling photo list, where list is loaded part by part via GraphQL. But when user opens some photo and eventually returns to photo list, it is restored from Redux to same state and scroll position as it was before opening the photo.
I have spent countless hours trying to get it work, but this useEffect-thing is spoiling my every attempt. :) They always gets triggered before I want them to trigger, because there is so many changing variables.
Also, sometimes I want to run a function within useEffect (function added to dependency array), and I use useCallback for that function to memoize it. But then I also have to add all variables that function uses to dependency array of that useCallback, so function gets regenerated when those variables changes. That means that useEffect suddenly runs again, because the function in dependency array changes.
Is there really no way to use functions/variables in useEffect, without them to trigger useEffect?
It all depends on how updatePhotos works. If that creates an action then the problem is you are creating the new state in the wrong place. The previous value of photos shouldn’t be used here because as you pointed out, that causes a dependency.
Instead your reducer will have the old value of photos you can use and you simply pass the new request data to your reducer.
Described in more detail here: https://overreacted.io/a-complete-guide-to-useeffect/#decoupling-updates-from-actions
You can have two separate useEffect functions inside the same component and they will work independent one of another. use one for photos and one for data loading. I hope this example helps you to wrap your head around this.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [step, setStep] = useState(1);
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
useEffect(() => {
const id = setInterval(() => {
setCount2((c) => c + step);
}, 1500);
return () => clearInterval(id);
}, [step]);
return (
<div>
<h1>{count}</h1>
<h1>{count2}</h1>
<input value={step} onChange={(e) => setStep(Number(e.target.value))} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
Please refer to this example in sandbox
https://codesandbox.io/s/react-playground-forked-6h0oz

how to use setInterval without useEffect in react

if i used setInterval(line 15) without useEffect than it gives result 2^n-1(0,1,3,7,15,31,63...) instead of(0,1,2,3,4,..) . so i have some question
1)why i am getting that output when I directly called setInterval without useeffect 2)is there any way if I change setCount(line 9) and its gives correct output by use setInterval directly without useEffect(as I did)
3) if the use of setInterval is not possible without useEffcet than why it is not possible?
if i put setInterval in useEffect and render initially once( line 12,13,14) than it gives correct output.....
but I do not get the correct output when I use directly setInterval. what is diff bet them?
in both cases, I call setInterval once but the output is diff.
import React, {useEffect, useState } from 'react'
export default function IncorrectDependency() {
const [count,setCount]=useState(0)
const inc=()=>{
// console.log(count)
setCount(preVal=>preVal+1)
// setCount(count+1)
}
// useEffect(()=>{
// setInterval(inc,1000)},[]
// )
setInterval(inc,1000)
return (
<div>
<h1>{count}</h1>
</div>
)
}
When we do a set state, functional components will re-execute from top to bottom, how ever when we use useState, useCallbacks etc.. they will not re-initialize as variables, functions,
So in this case, setInterval will re-initialize on each and every setCount, because of the state got changed,
step by step
in the 1st second there will be one setInterval, and call setCount and component is ready to rerender
when re-redering, start executing functional component from top-to-bottom it sees setInterval again and it will trigger it, so now we have two setIntervals
so on it will add multiple setIntervals on each second, because we don't clear it, so you should see the number printed in the browser will not take a second, but less than a second when time goes by.
You can achieve the expected result without useEffect by clearing the previous interval on each re-render which is happen due to setCount
create a variable to hold the set interval, code
const interval = null;
//this should be declare out side the component,
//because if we declare it inside the component it will redeclare,
//and the reference to the previous setInterval will be lost in that case no-way to clear the setInterval.
export default function IncorrectDependency() {
....
if (interval) {
clearInterval(interval);
}
interval = setInterval(inc, 1000);
....
}
alternatively react has a hook which can hold the same variables without re-initializing on each renderings, check it out useRef
here is a code-demo
const intvl = useRef(null);
....
if (intvl?.current) {
clearInterval(intvl.current);
}
intvl.current = setInterval(inc, 1000);
.....
when you directly use setInterval what is happening as this is a function it will be called on state change so again a setInterval will be triggered and so on which actually give you the incorrect result, so you shouldn't use setInterval without use effect, also on unmount you should clearthe interval
Dan Abramov explains why this isn't a good idea:
"it’s not the idiomatic way to do it. eg it won’t work correctly if you have multiple instances of the same component. it breaks the rules — it does a side effect (setInterval) during rendering, which the page said you should not do :) once you break the rules, all bets are off"
https://twitter.com/dan_abramov/status/1577395235340095501?s=20&t=nNnYyjLvHs5by_dqF5l8zg

Why useEffect doesn't call callback function if state doesn't change?

I have the following code(react 16.8.6, react-dom: 16.8.6):
import React, { useState, useEffect, } from 'react';
import ReactDOM from 'react-dom';
function App(props) {
const [counter, setCounter] = useState(0);
console.log(1)
useEffect(() => {
console.log(2)
setTimeout(() => {
console.log(3)
setCounter(1);
}, 10000)
});
return counter;
}
ReactDOM.render(<App />, document.getElementById('root'));
When App component is rendered first time, it prints:
1
2
After 10 seconds it prints:
3, 1, 2
Everything that was before is understandable for me, but now after 10 seconds it prints
3, 1
i.e. function that is passed to useEffect isn't called. I assume it's related to state somehow (as it doesn't change, if it changes useEffect works fine). Could you explain this behaviour?
According to the docs
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
An update never occurred after you call setCounter a second time, because 1 === 1 is always the same.
If you actually increment your counter by one every time you'll get your desired effect.
function App(props) {
const [counter, setCounter] = useState(0);
console.log(1)
useEffect(() => {
console.log(2)
setTimeout(() => {
console.log(3)
setCounter(counter + 1);
}, 10000)
});
return counter;
}
Live example
From the React docs:
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. (React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
Thats exactly what happens here: The component rerenders, but as the current state is 1 and you set it to 1 after the second timeout React "bails out" and thus does not retrigger the effect.
Sidenote: The effect should really only depend on counter, not on an update in general.
Every time your component renders, useEffect will be called because you do not provide an array as a second parameter.
If you read the docs, you will read, that if you do not provide a second parameter, it will be called every time.
If you provide an empty array, it will only be called on the first render.
If you provide a variable into the array, it will only be executed if that variable changes.
3,1 will be called because after the 2 is called, the timeout is set again and will print 3. After updating the state with setCounter(1);, the component rerenders and 1 will be called again.
Hope this helps. Happy coding.

Categories

Resources