I'm trying to learn how to use intervalRef where I increment the state ever 100 ms, but for some reason it does not work.
const {useState,useEffect,useRef} = React;
function Timer({active}) {
const intervalRef = useRef(null)
const [count, setCount] = useState(0)
useEffect(()=>{
if(active){
intervalRef.current = setInterval(()=>{
console.log(count);
setCount(count + 1);
},100)
} else {
clearInterval(intervalRef.current)
}
},[active])
return (
<p>{count}</p>
)
}
function Main() {
const [active, setActive] = useState(false)
return (
<div>
<Timer active={active}/>
<button onClick={()=>{setActive(!active)}}>Toggle</button>
</div>
)
}
ReactDOM.render(<Main />, document.getElementById('app'));
<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="app"></div>
The interval works completely fine since the console.log(count) prints ok, but why doesn't setCount work?
Since the useEffect is not dependant on count, the count inside the closure is always 0, and 0 + 1 -> 1. Use an updater function when you call setState. The update function is called with the current state.
Note: you should also return a cleanup function from useEffect, that will clear the interval if the component is unmounted.
const { useState, useEffect, useRef } = React;
function Timer({ active }) {
const intervalRef = useRef(null);
const [count, setCount] = useState(0);
useEffect(
() => {
if (active) {
intervalRef.current = setInterval(() => {
setCount(count => count + 1);
}, 100);
} else {
clearInterval(intervalRef.current);
}
return () => clearInterval(intervalRef.current); // cleanup function
},
[active]
);
return <p>{count}</p>;
}
function Main() {
const [active, setActive] = useState(false);
return (
<div>
<Timer active={active} />
<button onClick={() => { setActive(!active); }}>Toggle</button>
</div>
);
}
ReactDOM.render(<Main />, document.getElementById("app"));
<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="app"></div>
Related
This question already has answers here:
Cleanup ref issues in React
(2 answers)
Closed 10 months ago.
I'm trying to access a ref during clean up (before the component unmounts).
Like so:
const Comp = () => {
const imgRef = React.useRef();
React.useEffect(() => {
console.log('component mounted', imgRef); // During mount, imgRef.current is always defined
return () => {
console.log('component unmounting', imgRef); // imgRef.current always null here
}
}, []); // also tried adding imgRef and imgRef.current still doesn't work in clean up
return (
<img src={'example.png'} ref={imgRef} />
);
};
const App = () => {
const [img, setImg] = React.useState(true);
return <div>
<button onClick={() => setImg(!img)}>toggle</button>
{img && <Comp />}
</div>;
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
Even adding imgRef in the useEffect's dependency, the imgRef.current is still null in the return of useEffect...
This works in the equivalent Class component with componentWillUnmount the imgRef is properly defined.
How can I make it work with hooks?
This was very helpful: https://stackoverflow.com/a/67069936
Something like this worked for me:
const imgRef = useRef();
useEffect(() => {
let localRef = null;
if (imgRef.current) localRef = imgRef.current;
return () => {
console.log('component unmounting', localRef); // localRef works here!
}
}, []);
return (
<img ref={imgRef} src="example.png" />
);
I have a React app, there's a count increase every second, click on the button will alert the latest count after 2 seconds.
expect: click on the button at 10 seconds, the alert should display 12 (the latest count after 2 seconds).
actual: click on the button at 10 seconds, the alert displays 10 (stale closure issue)
I solved this issue by using "Class component" but I want to understand the Stale closure issue of "Function component".
function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setCount(c => c + 1);
}, 1000);
}, []);
const showCurrentCount = () => {
setTimeout(() => {
alert(count);
}, 2000);
};
return (
<div>
<h1>{count}</h1>
<button onClick={() => showCurrentCount()}>show</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
You can fix the stale value issue by using the callback method of setState. Inside the callback, alert the count return the same count.
const showCurrentCount = () => {
setTimeout(() => {
setCount((count) => {
alert(count);
return count;
});
}, 2000);
};
Demo
function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setCount(c => c + 1);
}, 1000);
}, []);
const showCurrentCount = () => {
setTimeout(() => {
setCount((count) => {
alert(count);
return count;
});
}, 2000);
};
return (
<div>
<h1>{count}</h1>
<button onClick={() => showCurrentCount()}>show</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
I just fixed my issue by using useRef.
React documentation:
https://reactjs.org/docs/hooks-reference.html#useref
function App() {
const refCount = React.useRef();
const [count, setCount] = React.useState(0);
refCount.current = count;
React.useEffect(() => {
setInterval(() => {
setCount(c => c + 1);
}, 1000);
}, []);
const showCurrentCount = () => {
setTimeout(() => {
alert(refCount.current);
}, 2000);
};
return (
<div>
<h1>{count}</h1>
<button onClick={showCurrentCount}>show</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
I have a pretty simple useEffect hook
const [tagsWithData, setTagsWithData] = useState([]);
useEffect(() => {
....
const finalsTags = temp.map((item) => item.name);
setTagsWithData(finalsTags);
}, []);
Inside of return, I have condition to render the input tag
{tagsWithData.length !== 0 ? (
<TagsInput
selectedTags={selectedTags}
tags={tagsWithData}
/>
) : (
<TagsInput
selectedTags={selectedTags}
tags={tags}
/>
)}
The above code always stays on 0 and it does not move to the else condition.
What am I making wrong here.
Thank you
Your useEffect is not being told to update. useEffect needs to be passed the value/dependencies that it needs to (trigger the) update on. Without it, the effect will only run once on (initial) component render
const [tagsWithData, setTagsWithData] = useState([]);
useEffect(() => {
....
const finalsTags = temp.map((item) => item.name);
setTagsWithData(finalsTags);
}, [temp]); // <--- add this
Below is a small example illustrating the differences. Click on the button, and check out the output of both effectWithDep and effectWithoutDep. You'll notice only effectWithDep will update.
// Get a hook function
const { useState, useEffect } = React;
const Example = ({title}) => {
const [count, setCount] = useState(0);
const [effectWithDep, setEffectWithDep] = useState(0);
const [effectWithoutDep, setEffectWithoutDep] = useState(0);
useEffect(() => {
setEffectWithDep(count)
}, [count])
useEffect(() => {
setEffectWithoutDep(count)
}, [])
return (
<div>
<p>{title}</p>
<p>effectWithDep: {effectWithDep}</p>
<p>effectWithoutDep: {effectWithoutDep}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="Example using Hooks:" />,
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>
Is there any api that allow us to write code something like this:
const MyComponents = () => {
const [number, setNumber] = useState(0);
return {
Btn: <Button onPress={() => setNumber(number + 1)}>
{number}
</Button>,
Log: <p>{number}</p>
}
}
const Perent = () => <>
<div ...>
<MyComponents.Btn/>
...
...
</div>
<MyComponents.Log/>
</>
Some kind of ability to group some Component.And render them in different places...
Seems like this would be better achieved by using a Context.
E.g.
const { createContext, useState, useContext } = React;
const CountContext = createContext();
const CountContainer = ({ children }) => {
const [number, setNumber] = useState(0);
return <CountContext.Provider value={{ number, setNumber }}>
{children}
</CountContext.Provider>
};
const CountButton = () => {
const { number, setNumber } = useContext(CountContext);
return <button onClick={() => setNumber((c) => c + 1)}>
{number}
</button>;
};
const CountLog = () => {
const { number } = useContext(CountContext);
return <p>{number}</p>;
};
const SomeCountButtons = () => <div><CountButton /><CountButton /></div>;
const App = () => (<div>
<CountContainer>
<CountButton />
<CountLog />
</CountContainer>
<CountContainer>
<SomeCountButtons />
<CountLog />
</CountContainer>
</div>);
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>
Then any <CountButton>s or <CountLog>s that occur anywhere within the same <CountContainer> will be able to share their state.
How to fetch time when submit button is clicked from app component. I have two separate component a Timer component and a App component.
when i click on submit button from app component it should take current time snapshot from timer component and send it to app component
I don't want to use Submit in timer component
Is it possible to do in React
?
Here is app component
export default function App() {
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
<h3>Time: </h3>
<button >Submit</button>
<Timer />
</div>
);
}
Here is Timer Component
export default function Time() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
let countersystem;
countersystem = setTimeout(() => setCounter(counter + 1), 1000);
return () => {
clearTimeout(countersystem);
};
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter}</div>
</div>
);
}
Here you go :
You have to take 2 state on App level
1 : snapshotTrigger to trigger to timer component get current snapshot via useEffect
2 : snapshot maintain last/current snapshot
You can run the below code snippet, hope that will clear your doubts :
const { useState , useEffect } = React;
function Timer({snapshotTrigger , getSnapshot }) {
const [counter, setCounter] = useState(0);
// ------------ START : ADDED -----------
useEffect(() => {
if (snapshotTrigger) {
getSnapshot(counter);
}
}, [snapshotTrigger]);
// ------------ END : ADDED -----------
useEffect(() => {
let countersystem;
countersystem = setTimeout(() => setCounter(counter + 1), 1000);
return () => {
clearTimeout(countersystem);
};
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter}</div>
</div>
);
}
function App() {
// ------------ START : ADDED -----------
const [snapshotTrigger, setSnapshotTrigger] = useState(0);
const [snapshot, setSnapshot] = useState(0);
// ------------ START : END -----------
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
<h3>Time: {snapshot}</h3>
<button onClick={() => setSnapshotTrigger(snapshotTrigger + 1)}>Submit</button>
<Timer snapshotTrigger={snapshotTrigger} getSnapshot={setSnapshot} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('react-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="react-root"></div>
To achieve what you want I added two states to the <App> component (counter and trigger) and sent the counter set state function of the <App> component to the <Timer>, and used trigger state variable to check if the timer should continue or not, here is a snippet of what I did and also you can check this snippet for working sample:
import React from "react";
import "./styles.css";
function Timer({ onTick, active }) {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
let countersystem;
countersystem = setTimeout(() => setCounter(counter + 1), 1000);
return () => {
clearTimeout(countersystem);
};
}, [counter]);
React.useEffect(() => {
if (active) {
onTick(counter);
}
}, [active]);
return (
<div className="App">
<div>Countdown: {counter}</div>
</div>
);
}
export default function App() {
const [counter, setCounter] = React.useState(0);
const [tigger, setTrigger] = React.useState(0);
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
<h3>Time: {counter}</h3>
<button
onClick={() => {
setTrigger(tigger + 1);
}}
>
Submit
</button>
<Timer onTick={setCounter} active={tigger} />
</div>
);
}