problems in useEffect hook - reactjs

Let's say I have a state called count.
const [count, setCount] = useState();
Now let's say I would like to increase count with 1 every time some key in the keyboard is being pressed.
So I can use useEffect hook and add an event listener to it.
useEffect(() => {
document.addEventListener("keydown", increaseCount);
}, []);
The function increaseCount, increasing count with 1
const increaseCount = () => {
setCount(prevCount => prevCount + 1);
};
Well everything is working great. BUT, if I want to get the current value of count inside increaseCount, I can't do this! The event listener is only called once when the component is mounting (because the useEffect has an empty dependency array).
And if I add count to the dependency array, I have a new problem - I'm creating a kind of loop, because useEffect will call increaseCount, that will call setCount(), that will cause the component to re-render, which will call useEffect again and so on and so on.
I have this kind of problem on a few projects I'm currently working on, and it is very frustrating. So if you know how to answer this - thanks! :)
snippets
When using an empty dependency array and login count inside increaseCount, count will always be 0:
// Get a hook function
const {useState, useEffect} = React;
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.addEventListener("keydown", increaseCount);
}, []);
const increaseCount = () => {
setCount((prevCount) => prevCount + 1);
console.log(count);
};
return (
<div>
count = {count}
</div>
);
};
// Render it
ReactDOM.render(
<Counter />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
and when adding count to the dependency array, we see this "loop" thing happening:
// Get a hook function
const {useState, useEffect} = React;
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.addEventListener("keydown", increaseCount);
}, [count]);
const increaseCount = () => {
setCount((prevCount) => prevCount + 1);
console.log(count);
};
return (
<div>
count = {count}
</div>
);
};
// Render it
ReactDOM.render(
<Counter />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

What you should do really depends on what you need to do. See X Y problem.
As mentioned there are multiple use cases you are trying for.
const { useState, useEffect, useMemo, Fragment } = React;
const App = () => {
const [count, setCount] = useState(0);
function increaseCount(ev) {
setCount((count) => {
const newCount = count + 1;
// You can use the newCount here for something basic if needed...
console.log(newCount);
// I'm not positive, but I'm farily sure that setting other state
// from within a set state function might be problematic.
// If you feel the need to do something with side effects here,
// Consider another useEffect as below.
return newCount;
});
}
useEffect(() => {
document.addEventListener("keydown", increaseCount);
// Always clean up after your effect!
return () => document.removeEventListener("keydown", increaseCount);
// If you use `increaseCount` here as a dependency (which you should), the function will get recreated every time.
// Or you could set it up by creating the `increase count function inside the effect itself
}, [increaseCount]);
useEffect(() => {
// If you need to use newCount for something more complicated, do it here...
// Or for side effects
}, [count]);
const arrayCountSized = useMemo(() => {
// You can use the count here in a useMemo for things that are
// derived, but not stateful in-of-themselves
return new Array(count).fill(null).map((_, idx) => idx);
}, [count]);
return (
<Fragment>
<div>{count}</div>
<ul>
{arrayCountSized.map((row) => (
<li key={row}>{row}</li>
))}
</ul>
</Fragment>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"/>
As for the situation you mentioned in particular, here's one solution.
You can get the "current Index" from the array length normally, which is what I'm doing here.
If the "currentIndex" is more complicated than that and inexorably tied together with the array state, you should useReducer to set up that tied state in a pure fashion.
You could also use a reference to the array in your listener function.
const arrayRef = useRef(array);
useEffect(()=>{arrayRef.current=array},[array]);
// arrayRef.current is safe to use in any effect after this.
const { useState, useEffect, useMemo, Fragment } = React;
const App = () => {
const [array, setArray] = useState([]);
const curIndex = array.length - 1;
useEffect(() => {
function addKey(ev) {
if (ev.key.length === 1 && ev.key >= "a" && ev.key <= "z") {
setArray((arr) => [...arr, ev.key]);
}
}
document.addEventListener("keydown", addKey);
// Always clean up after your effect!
return () => document.removeEventListener("keydown", addKey);
}, []);
return (
<Fragment>
<div>Current Index: {curIndex}</div>
<div>Keys pressed: </div>
<ul>
{array.map((row, index) => (
<li key={index}>{row}</li>
))}
</ul>
</Fragment>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"/>
useReducer Approach:
As I don't have enough details, this is the same solution as above, just in a reducer.
const { useState, useEffect, useReducer, useMemo, Fragment } = React;
function reducer(state, key) {
if (!key) {
return state;
}
const array = [...state.array, key];
return { array, currentIndex: array.length - 1 };
}
const App = () => {
const [{ array, currentIndex }, addKey] = useReducer(reducer, {
array: [],
currentIndex: -1,
});
useEffect(() => {
function keydownListener(ev) {
if (ev.key.length === 1 && ev.key >= "a" && ev.key <= "z") {
addKey(ev.key);
}
}
document.addEventListener("keydown", keydownListener);
// Always clean up after your effect!
return () => document.removeEventListener("keydown", keydownListener);
}, []);
return (
<Fragment>
<div>Current Index: {currentIndex}</div>
<div>Keys pressed: </div>
<ul>
{array.map((row, index) => (
<li key={index}>{row}</li>
))}
</ul>
</Fragment>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"/>

Related

Why is clearInterval not stopping my timer?

I am trying to build a timer with three buttons, a start, stop, where it stops on the current integer, and a reset. I have added my code below which results in the following issues.
I thought my stop function would stop the timer from decrementing but it continues to do so. Also, when logging my timer state in the console, you can see it does not update in the console even though it is updating in the DOM. Why is this?
Thank you for any insight at all.
import React from 'react';
import './style.css';
export default function App() {
const [timer, setTimer] = React.useState(50);
const reset = () => {
setTimer(50);
};
const start = () => {
setTimer((prev) => prev - 1);
};
// const interval = setInterval(() => {
// console.log(updated)
// //start() }, 1000)
// }
const interval = () => {
setInterval(() => {
console.log('updated');
console.log(timer);
start();
}, 1000);
};
const stop = () => {
clearInterval(start);
};
return (
<div>
<h1>{timer}</h1>
<button onClick={interval}>start</button>
<button onClick={stop}>stop</button>
<button onClick={reset}>reset</button>
</div>
);
}`
You have a small problem with assigning the actual value for the interval.
Here is how it should be in usage
const interval = setInterval(() => {})
clearInterval(interval)
For your code change, you can create a ref to keep the interval variable and use it to clean up the interval later.
function App() {
const [timer, setTimer] = React.useState(5);
const intervalRef = React.useRef(); //create a ref for interval
const reset = () => {
setTimer(5);
};
const start = () => {
setTimer((prev) => {
if(prev === 0) {
stop();
return 0;
}
return prev - 1;
});
};
// const interval = setInterval(() => {
// console.log(updated)
// //start() }, 1000)
// }
const interval = () => {
//assign interval ref here
intervalRef.current = setInterval(() => {
start();
}, 1000);
};
const stop = () => {
//clear the interval ref
clearInterval(intervalRef.current);
};
return (
<div>
<h1>{timer}</h1>
<button onClick={interval}>start</button>
<button onClick={stop}>stop</button>
<button onClick={reset}>reset</button>
</div>
);
}
ReactDOM.render(
<App/>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
clearTimeout or clearInterval each take a token, which was returned by a previous call to setTimeout or setInterval. So you'll need to store that token:
const id = setInterval(() => console.log("triggered"), 1000);
// ...
clearInterval(id)
Also, you should be careful about what happens if App is re-rendered, so you should probably put the set/clear logic inside useEffect so you can cleanup your interval.
Also also, although you didn't ask, your console.log(timer) isn't going to work, and will always print 50. The timer variable inside that callback is captured once, and is never updated because that callback is just inside the setInterval now. You'll need to clear and reset your interval with an updated callback function every time App re-renders, or use a ref that you keep updated, which is a pain.
I would recommend borrowing this custom hook that considers all of these things for you: https://usehooks-ts.com/react-hook/use-interval
Then your App component could become extremely simple, but still be robust:
const { useEffect, useRef, useState, useLayoutEffect } = React;
// https://usehooks-ts.com/react-hook/use-interval
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useLayoutEffect(() => {
savedCallback.current = callback;
}, [callback])
useEffect(() => {
if (!delay && delay !== 0) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [timer, setTimer] = useState(50);
const [running, setRunning] = useState(false);
useInterval(() => setTimer(t => t - 1), running ? 1000 : null);
const start = () => setRunning(true);
const stop = () => setRunning(false);
const reset = () => { setTimer(50); };
return (
<div>
<h1>{timer}</h1><button onClick={start}>start</button>
<button onClick={stop}> stop </button>
<button onClick={reset}> reset </button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
I think you should not wrap your setTimeout into a callable. Then you lose the ability to start and stop it because the variable does not reference the interval but the callable that wraps the interval.
Take a look at this guide: https://www.w3schools.com/jsref/met_win_clearinterval.asp

What is the good practice way to prevent a useEffect from triggering on initial render? [duplicate]

According to the docs:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
We can use the new useEffect() hook to simulate componentDidUpdate(), but it seems like useEffect() is being ran after every render, even the first time. How do I get it to not run on initial render?
As you can see in the example below, componentDidUpdateFunction is printed during the initial render but componentDidUpdateClass was not printed during the initial render.
function ComponentDidUpdateFunction() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
class ComponentDidUpdateClass extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidUpdate() {
console.log("componentDidUpdateClass");
}
render() {
return (
<div>
<p>componentDidUpdateClass: {this.state.count} times</p>
<button
onClick={() => {
this.setState({ count: this.state.count + 1 });
}}
>
Click Me
</button>
</div>
);
}
}
ReactDOM.render(
<div>
<ComponentDidUpdateFunction />
<ComponentDidUpdateClass />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
We can use the useRef hook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffect function is being run.
If we want the effect to run in the same phase that componentDidUpdate does, we can use useLayoutEffect instead.
Example
const { useState, useRef, useLayoutEffect } = React;
function ComponentDidUpdateFunction() {
const [count, setCount] = useState(0);
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<ComponentDidUpdateFunction />,
document.getElementById("app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
You can turn it into custom hooks, like so:
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) func();
else didMount.current = true;
}, deps);
}
export default useDidMountEffect;
Usage example:
import React, { useState, useEffect } from 'react';
import useDidMountEffect from '../path/to/useDidMountEffect';
const MyComponent = (props) => {
const [state, setState] = useState({
key: false
});
useEffect(() => {
// you know what is this, don't you?
}, []);
useDidMountEffect(() => {
// react please run me if 'key' changes, but not on initial render
}, [state.key]);
return (
<div>
...
</div>
);
}
// ...
I made a simple useFirstRender hook to handle cases like focussing a form input:
import { useRef, useEffect } from 'react';
export function useFirstRender() {
const firstRender = useRef(true);
useEffect(() => {
firstRender.current = false;
}, []);
return firstRender.current;
}
It starts out as true, then switches to false in the useEffect, which only runs once, and never again.
In your component, use it:
const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);
useEffect(() => {
if (firstRender || errors.phoneNumber) {
phoneNumberRef.current.focus();
}
}, [firstRender, errors.phoneNumber]);
For your case, you would just use if (!firstRender) { ....
Same approach as Tholle's answer, but using useState instead of useRef.
const [skipCount, setSkipCount] = useState(true);
...
useEffect(() => {
if (skipCount) setSkipCount(false);
if (!skipCount) runYourFunction();
}, [dependencies])
EDIT
While this also works, it involves updating state which will cause your component to re-render. If all your component's useEffect calls (and also all of its children's) have a dependency array, this doesn't matter. But keep in mind that any useEffect without a dependency array (useEffect(() => {...}) will be run again.
Using and updating useRef will not cause any re-renders.
#ravi, yours doesn't call the passed-in unmount function. Here's a version that's a little more complete:
/**
* Identical to React.useEffect, except that it never runs on mount. This is
* the equivalent of the componentDidUpdate lifecycle function.
*
* #param {function:function} effect - A useEffect effect.
* #param {array} [dependencies] - useEffect dependency list.
*/
export const useEffectExceptOnMount = (effect, dependencies) => {
const mounted = React.useRef(false);
React.useEffect(() => {
if (mounted.current) {
const unmount = effect();
return () => unmount && unmount();
} else {
mounted.current = true;
}
}, dependencies);
// Reset on unmount for the next mount.
React.useEffect(() => {
return () => mounted.current = false;
}, []);
};
a simple way is to create a let, out of your component and set in to true.
then say if its true set it to false then return (stop) the useEffect function
like that:
import { useEffect} from 'react';
//your let must be out of component to avoid re-evaluation
let isFirst = true
function App() {
useEffect(() => {
if(isFirst){
isFirst = false
return
}
//your code that don't want to execute at first time
},[])
return (
<div>
<p>its simple huh...</p>
</div>
);
}
its Similar to #Carmine Tambasciabs solution but without using state :)
‍‍‍‍‍‍
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
function useEffectAfterMount(effect, deps) {
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) return effect();
else isMounted.current = true;
}, deps);
// reset on unmount; in React 18, components can mount again
useEffect(() => {
isMounted.current = false;
});
}
We need to return what comes back from effect(), because it might be a cleanup function. But we don't need to determine if it is or not. Just pass it on and let useEffect figure it out.
In an earlier version of this post I said resetting the ref (isMounted.current = false) wasn't necessary. But in React 18 it is, because components can remount with their previous state (thanks #Whatabrain).
I thought creating a custom hook would be overkill and I didn't want to muddle my component's readability by using the useLayoutEffect hook for something unrelated to layouts, so, in my case, I simply checked to see if the value of my stateful variable selectedItem that triggers the useEffect callback is its original value in order to determine if it's the initial render:
export default function MyComponent(props) {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {
if(!selectedItem) return; // If selected item is its initial value (null), don't continue
//... This will not happen on initial render
}, [selectedItem]);
// ...
}
This is the best implementation I've created so far using typescript. Basically, the idea is the same, using the Ref but I'm also considering the callback returned by useEffect to perform cleanup on component unmount.
import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';
/**
* #param effect
* #param dependencies
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependencies?: DependencyList
) {
//Preserving the true by default as initial render cycle
const initialRender = useRef(true);
useEffect(() => {
let effectReturns: void | (() => void) = () => {};
// Updating the ref to false on the first render, causing
// subsequent render to execute the effect
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}
// Preserving and allowing the Destructor returned by the effect
// to execute on component unmount and perform cleanup if
// required.
if (effectReturns && typeof effectReturns === 'function') {
return effectReturns;
}
return undefined;
}, dependencies);
}
You can simply use it, as usual as you use the useEffect hook but this time, it won't run on the initial render. Here is how you can use this hook.
useNoInitialEffect(() => {
// perform something, returning callback is supported
}, [a, b]);
If you use ESLint and want to use the react-hooks/exhaustive-deps rule for this custom hook:
{
"rules": {
// ...
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useNoInitialEffect"
}]
}
}
#MehdiDehghani, your solution work perfectly fine, one addition you have to do is on unmount, reset the didMount.current value to false. When to try to use this custom hook somewhere else, you don't get cache value.
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
let unmount;
if (didMount.current) unmount = func();
else didMount.current = true;
return () => {
didMount.current = false;
unmount && unmount();
}
}, deps);
}
export default useDidMountEffect;
Simplified implementation
import { useRef, useEffect } from 'react';
function MyComp(props) {
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
} else {
myProp = 'some val';
};
}, [props.myProp])
return (
<div>
...
</div>
)
}
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
For people who are having trouble with React 18 strict mode calling the useeffect on the initial render twice, try this:
// The init variable is necessary if your state is an object/array, because the == operator compares the references, not the actual values.
const init = [];
const [state, setState] = useState(init);
const dummyState = useRef(init);
useEffect(() => {
// Compare the old state with the new state
if (dummyState.current == state) {
// This means that the component is mounting
} else {
// This means that the component updated.
dummyState.current = state;
}
}, [state]);
Works in development mode...
function App() {
const init = [];
const [state, setState] = React.useState(init);
const dummyState = React.useRef(init);
React.useEffect(() => {
if (dummyState.current == state) {
console.log('mount');
} else {
console.log('update');
dummyState.current = state;
}
}, [state]);
return (
<button onClick={() => setState([...state, Math.random()])}>Update state </button>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
And in production.
function App() {
const init = [];
const [state, setState] = React.useState(init);
const dummyState = React.useRef(init);
React.useEffect(() => {
if (dummyState.current == state) {
console.log('mount');
} else {
console.log('update');
dummyState.current = state;
}
}, [state]);
return (
<button onClick={() => setState([...state, Math.random()])}>Update state </button>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="app"></div>
If you want to skip the first render, you can create a state "firstRenderDone" and set it to true in the useEffect with empty dependecy list (that works like a didMount). Then, in your other useEffect, you can check if the first render was already done before doing something.
const [firstRenderDone, setFirstRenderDone] = useState(false);
//useEffect with empty dependecy list (that works like a componentDidMount)
useEffect(() => {
setFirstRenderDone(true);
}, []);
// your other useEffect (that works as componetDidUpdate)
useEffect(() => {
if(firstRenderDone){
console.log("componentDidUpdateFunction");
}
}, [firstRenderDone]);
All previous are good, but this can be achieved in a simplier way considering that the action in useEffect can be "skipped" placing an if condition(or any other ) that is basically not run first time, and still with the dependency.
For example I had the case of :
Load data from an API but my title has to be "Loading" till the date were not there, so I have an array, tours that is empty at beginning and show the text "Showing"
Have a component rendered with different information from those API.
The user can delete one by one those info, even all making the tour array empty again as the beginning but this time the API fetch is been already done
Once the tour list is empty by deleting then show another title.
so my "solution" was to create another useState to create a boolean value that change only after the data fetch making another condition in useEffect true in order to run another function that also depend on the tour length.
useEffect(() => {
if (isTitle) {
changeTitle(newTitle)
}else{
isSetTitle(true)
}
}, [tours])
here my App.js
import React, { useState, useEffect } from 'react'
import Loading from './Loading'
import Tours from './Tours'
const url = 'API url'
let newTours
function App() {
const [loading, setLoading ] = useState(true)
const [tours, setTours] = useState([])
const [isTitle, isSetTitle] = useState(false)
const [title, setTitle] = useState("Our Tours")
const newTitle = "Tours are empty"
const removeTours = (id) => {
newTours = tours.filter(tour => ( tour.id !== id))
return setTours(newTours)
}
const changeTitle = (title) =>{
if(tours.length === 0 && loading === false){
setTitle(title)
}
}
const fetchTours = async () => {
setLoading(true)
try {
const response = await fetch(url)
const tours = await response.json()
setLoading(false)
setTours(tours)
}catch(error) {
setLoading(false)
console.log(error)
}
}
useEffect(()=>{
fetchTours()
},[])
useEffect(() => {
if (isTitle) {
changeTitle(newTitle)
}else{
isSetTitle(true)
}
}, [tours])
if(loading){
return (
<main>
<Loading />
</main>
)
}else{
return (
<main>
<Tours tours={tours} title={title} changeTitle={changeTitle}
removeTours={removeTours} />
</main>
)
}
}
export default App
const [dojob, setDojob] = useState(false);
yourfunction(){
setDojob(true);
}
useEffect(()=>{
if(dojob){
yourfunction();
setDojob(false);
}
},[dojob]);

I want to subtract the value of 'percent' from the function by 0.25 in React

I want to subtract the value of 'percent' from the function by 0.25.
However, subtraction does not work.
I used setState, but I don't know why it doesn't work.
import React, {useState, useRef, useCallback} from 'react';
const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);
const start = useCallback(() =>{
if (intervalRef.current !== null){
return;
}
intervalRef.current = setInterval(()=>{
if (percent > 0){
setPercent(c => c - 0.25);
console.log("percent = ", percent);
}
else {
setPercent(c => 1);
}
}, 1000);
}, []);
return (
<div>
<button onClick={()=>{start()}}>{"Start"}</button>
</div>
);
}
export default Ques;
Issue
The enqueued state updates are working correctly but you've a stale enclosure over the percent state in the interval callback that you are logging, it never will update.
Solution
If you want to log the percent state then use an useEffect hook to log changes.
const Ques = () => {
const [percent, setPercent] = useState(1);
const intervalRef = useRef(null);
useEffect(() => {
console.log("percent = ", percent); // <-- log state changes here
}, [percent]);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
setPercent((c) => Math.max(0, c - 0.25)); // <-- simpler updater function
}, 1000);
}, []);
return (
<div>
Percent: {percent * 100}
<button onClick={start}>Start</button>
</div>
);
};
You can create a ref for percent also and chenge its current value as:
codesandbox link
import React, { useRef, useCallback } from "react";
const Ques = () => {
const percentRef = useRef(1);
const intervalRef = useRef(null);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
console.log("percent = ", percentRef.current);
percentRef.current > 0
? (percentRef.current -= 0.25)
: (percentRef.current = 1);
}, 1000);
}, []);
return (
<div>
<button onClick={start}>Start</button>
</div>
);
};
export default Ques;
I think useCallback and useRef is not a good fit. Below is a minimal verifiable example using useState and useEffect. Note this function appropriately performs cleanup on the timer when the component is unmounted. Click Run to run the code snippet and click start to begin running the effect.
function App() {
const [percent, setPercent] = React.useState(1)
const [running, setRunning] = React.useState(false)
React.useEffect(() => {
if (!running) return
const t = window.setTimeout(() => {
setPercent(c => c > 0 ? c - 0.25 : 1)
}, 1000)
return () => window.clearTimeout(t)
}, [running, percent])
return <div>
<button onClick={() => setRunning(true)} children="start" />
<pre>percent: {percent}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
This may be one possible solution to achieve what is presumed to be the desired objective:
Code Snippet
const {useState, useRef, useCallback} = React;
const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);
const start = useCallback((flag) => {
if (intervalRef.current !== null){
if (flag && flag === 'end') clearInterval(intervalRef.current);
return;
}
intervalRef.current = setInterval(() => {
setPercent(
prev => (prev > 0 ? prev - 0.25 : 1)
);
}, 1000);
}, []);
return (
<div>
percent: {percent} <br/> <br/>
<button onClick={() => start('bgn')}>Start</button>  
<button onClick={() => start('end')}>Stop</button>
</div>
);
}
ReactDOM.render(
<div>
<h3>DEMO</h3>
<Ques />
</div>,
document.getElementById('rd')
);
<div id='rd' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
There are two buttons Start and Stop
Both invoke the same start method, but with different params (flag)
If intervalRef is already set (ie, not null) and flag is end, clear the interval
The percent is added to the UI to see real-time changes to its value
setPercent is modified to use prev (which holds the correct state)

React useEffect warning to put missing dependency. But dependency value changes in the hook

I want to change the value of formStepTouched when the value for step changes, so I am using useEffect. But useEffect throws a warning that its missing dependency of formStepTouched. As that is the value that's changing, putting that in the dependency array would cause infinite calls.
const [step, setStep] = useState(0);
const [formStepTouched, setFormStepTouched] = useState(
Array(childrenArray.length)
.fill(false)
.map((_, idx) => idx === 0)
)
useEffect(() => {
const newFormStepTouched = [...formStepTouched];
newFormStepTouched[step] = true;
setFormStepTouched(newFormStepTouched);
}, [step]);
Please refer to the codesandbox link below:
https://codesandbox.io/s/brave-gould-ymjt0?file=/src/App.js
As you can see the demo works perfectly however an error message is shown. If the dependecy is added useEffect will be called infinitely.
How to get rid of the error message.
You can pass a callback to state setter:
const { useEffect, useState } = React;
const App = () => {
const [step, setStep] = useState(0);
const [formStepTouched, setFormStepTouched] = useState(
Array(5)
.fill(false)
.map((_, idx) => idx === 0)
);
useEffect(() => {
//pass a callback to state setter
setFormStepTouched((formStepTouched) => {
if (formStepTouched[step] === true) {
//nothing to change
return formStepTouched;
}
return formStepTouched.map((s, i) =>
i === step ? true : s
);
});
}, [step]);
return (
<div>
<button onClick={() => setStep(step + 1)}>
nex step
</button>
<div>
<pre>
{JSON.stringify(formStepTouched, undefined, 2)}
</pre>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
useEffect(() => {
setFormStepTouched(prevState => {
const newFormStepTouched = [...prevState]
newFormStepTouched[step] = true
return newFormStepTouched
});
}, [step]);
The linter is telling you that have an external dependency that may change, you can read more here

Why my clearInterval in React doesn't work?

when I click to to change code I see inly consols.log. I try to understand it but I can't find the answer..
function App() {
const [laps, setLaps] = useState(0);
const [running, setRunning] = useState(false);
const startTime = Date.now() - laps;
useEffect(() => {
function interval() {
setInterval(() => {
setLaps(Date.now() - startTime);
}, 100);
}
if (!running) {
clearInterval(interval);
console.log('ok');
} else {
interval();
console.log('no');
}
console.log(running);
}, [running]);
return (
<div className="App">
<label>Count: {laps}</label>
<button onClick={() => setRunning(!running)}>{running ? 'stop' : 'start'}</button>
<button>Clear</button>
</div>
);
}
In clean JavaScript this code should works correctly(of course without JSX)?
clearInterval expects a number as argument that is returned from setInterval, but you are giving it the interval function as argument.
You could instead just create the interval if running is true, and return a cleanup function from useEffect that will be run the next time the effect is run.
const { useState, useEffect } = React;
function App() {
const [laps, setLaps] = useState(0);
const [running, setRunning] = useState(false);
const startTime = Date.now() - laps;
useEffect(
() => {
if (running) {
const interval = setInterval(() => {
setLaps(Date.now() - startTime);
}, 100);
return () => clearInterval(interval);
}
},
[running]
);
return (
<div className="App">
<label>Count: {laps}</label>
<button onClick={() => setRunning(!running)}>
{running ? "stop" : "start"}
</button>
<button>Clear</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

Resources