I have a React Native component where i'm calling for data in useEffect and rendering data accordingly. But i need to call for data after every 2 seconds and i'm doing that. But the whole component flickers after every two seconds. How can i stop that? Here's my component right now:
const Climate = () => {
const rooms: ControllerRoom<ClimateRoomItemType>[] =
useSelector(climateSelector);
const displayRoomList = useSelector(displayRoomListSelector);
//State
const [active, setActive] = useState<number>(-1);
const [count, setCount] = useState<number>(0);
const [isFetching, setIsFetching] = useState<boolean>(true);
//Handlers
const fetchClimate = useCallback(
() => dispatch(desktopAction.fetchClimate()),
[dispatch],
);
useLayoutEffect(() => {
fetchClimate();
fetchData();
}, []);
const fetchData = useCallback(
() => {
const interval = setInterval(() => {
fetchClimate();
}, 2000);
return () => {
fetchClimate();
clearInterval(interval);
};
},[]
);
useEffect(() => {
if (active < 0 && rooms.length > 0) {
setActive(newlist[0][0].id);
setIsFetching(false);
}
}, [rooms, isFetching]);
return(
<ControllerContainer>
{!displayRoomList ? (
<RoomsClimate
key={Math. floor(Math. random() * 100)}
rooms={rooms}
displayRoomList={displayRoomList}
/>
) : null}
</ControllerContainer>
)
}
export default memo(Climate);
How can i stop flickering of component and still ask for data every 2 seconds?
Related
I tried to create a simple timer app with ReactJS and found the below code on the internet.
Does the function that we passed to the useEffect will execute with the dependency change or does it recreates with every dependency change and then execute?
Also I console log the return function of the useEffect and it runs with every render. Does it run only when the component unmount? or with every render?
import { useEffect, useState } from "react";
const App = () => {
const [isActive, setIsActive] = React.useState(false);
const [isPaused, setIsPaused] = React.useState(true);
const [time, setTime] = React.useState(0);
React.useEffect(() => {
let interval = null;
if (isActive && isPaused === false) {
interval = setInterval(() => {
setTime((time) => time + 10);
}, 10);
} else {
clearInterval(interval);
}
return () => {
console.log("cleanup");
clearInterval(interval);
};
}, [isActive, isPaused]);
const handleStart = () => {
setIsActive(true);
setIsPaused(false);
};
const handlePauseResume = () => {
setIsPaused(!isPaused);
};
const handleReset = () => {
setIsActive(false);
setTime(0);
};
return (
<div className="stop-watch">
{time}
<button onClick={handleStart}>start</button>
<button onClick={handlePauseResume}>pause</button>
<button onClick={handleReset}>clear</button>
</div>
);
};
export default App;
The code inside the useEffect hook will run every time a dependency value has been changed. In your case whenever isActive or isPaused changes state.
This means that the reference to the interval will be lost, as the interval variable is redefined.
To keep a steady reference, use the useRef hook to have the reference persist throughout state changes.
const App = () => {
const [isActive, setIsActive] = useState(false);
const [isPaused, setIsPaused] = useState(true);
const [time, setTime] = useState(0);
const interval = useRef(null)
useEffect(() => {
if (isActive && !isPaused) {
interval.current = setInterval(() => {
setTime((time) => time + 10);
}, 10);
} else {
clearInterval(interval.current);
interval.current = null;
}
return () => {
clearInterval(interval.current);
};
}, [isActive, isPaused])
...
}
Here i want to render the AlertSound component when value of bitcoin state changes but it only render when i refresh application and not when value of state is changed. value of bitcoin state is getting update but the AlertSound component renders only once when app is reloaded
App.js:-
const App = () => {
const [bitcoin, setBitcoin] = useState();
const myRef = useRef("");
const fetchDetail = async () => {
const { data } = await Axios.get(
"https://api.coincap.io/v2/assets/bitcoin"
);
setBitcoin(Math.floor(data.data.priceUsd));
};
useEffect(() => {
const interval = setInterval(() => {
fetchDetail();
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<h1>{bitcoin}</h1>
{bitcoin < 41405 ? <AlertSound /> : " "}
</div>
);
};
export default App;
AlertSound.js:-
const AlertSound = () => {
const myRef = useRef(null);
return (
<div>
<audio ref={myRef} src={SoundFile} autoPlay />
</div>
);
};
Add dependency of bitcoin state in useEffect
const App = () =>{
const [bitcoin, setBitcoin] = useState();
const myRef = useRef("");
const fetchDetail = async () =>{
const {data} = await Axios.get("https://api.coincap.io/v2/assets/bitcoin");
setBitcoin(Math.floor(data.data.priceUsd));
}
useEffect(() => {
const interval = setInterval(() => {
fetchDetail();
}, 1000);
return () => clearInterval(interval);
}, [bitcoin]);
return(
<div>
<h1>{bitcoin}</h1>
{bitcoin < 41405 ? <AlertSound/> : " "}
</div>
)
}
export default App;
I have a React application which uses a Django backend, I have used webSocket to connect with the backend which updates state when there are some changes. But the changes are very rapid, so only the last changes are visible. I want to show the previous message for a certain time before next message is displayed. Here is my code
import React, { useEffect, useState, useRef } from "react";
const Text = () => {
const [message, setMessage] = useState("");
const webSocket = useRef(null);
useEffect(() => {
webSocket.current = new WebSocket("ws://localhost:8000/ws/some_url/");
webSocket.current.onmessage = (res) => {
const data = JSON.parse(res.data);
setMessage(data.message);
};
return () => webSocket.current.close();
}, []);
return <p>{message}</p>;
};
export default Text;
So the message should be visible for certain time (in seconds, for eg - 5 seconds), then the next message should be shown. Any idea how that could be done?
const Text = () => {
const [messages, setMessages] = useState([]);
const currentMessage = messages[0] || "";
const [timer, setTimer] = useState(null);
// webSocket ref missing? ;-)
useEffect(() => {
webSocket.current = new WebSocket("ws://localhost:8000/ws/some_url/");
webSocket.current.onmessage = (res) => {
const data = JSON.parse(res.data);
setMessages((prevState) => [ ...prevState, data.message]);
};
return () => webSocket.current.close();
}, []);
// Remove the current message in 5 seconds.
useEffect(() => {
if (timer || !messages.length) return;
setTimer(setTimeout(() => {
setMessages((prevState) => prevState.slice(1));
setTimer(null);
}, 5000));
}, [messages, timer]);
return <p>{currentMessage}</p>;
};
You can create a custom hook to handle the message transition. Pass as argument the desired time you want to wait before showing the next message. You can use it in other parts of your code:
useQueu.js
const useQueu = time => {
const [current, setCurrent] = useState(null); //--> current message
const queu = useRef([]); //--> messages
useEffect(() => {
const timeout = setTimeout(() => {
setCurrent(queu.current.shift());
}, time);
return () => clearTimeout(timeout);
}, [current]);
const add = obj => {
if (!current) setCurrent(obj); //--> don't wait - render immediately
else {
queu.current.push(obj);
}
};
return [current, add];
};
Text.js
const Text = () => {
const [message, add] = useQue(5000);
const webSocket = useRef(null);
useEffect(() => {
webSocket.current = new WebSocket("ws://localhost:8000/ws/some_url/");
webSocket.current.onmessage = (res) => {
const data = JSON.parse(res.data);
add(data.message); //--> add new message
};
return () => webSocket.current.close();
}, []);
return <p>{message}</p>;
};
Working example
I would like to setup a counter which can be paused as well as resumed in React.js. But whatever I have tried so far is working the functionality part (pause/resume is working) but it's not updating the counter on render. Below is my code:
const ProgressBar = (props) => {
const [isPlay, setisPlay] = useState(false);
const [progressText, setProgressText] = useState(
props.duration ? props.duration : 20
);
var elapsed,
secondsLeft = 20;
const timer = () => {
// setInterval for every second
var countdown = setInterval(() => {
// if allowed time is used up, clear interval
if (secondsLeft < 0) {
clearInterval(countdown);
return;
}
// if paused, record elapsed time and return
if (isPlay === true) {
elapsed = secondsLeft;
return;
}
// decrement seconds left
secondsLeft--;
console.warn(secondsLeft);
}, 1000);
};
timer();
const stopProgress = () => {
setisPlay(!isPlay);
if (isPlay === false) {
secondsLeft = elapsed;
}
};
return (
<>
<p>{secondsLeft}</p>
</>
);
};
export default ProgressBar;
I have tried React.js state, global var type, global let type, react ref so far to make the variable global but none of them worked..
So basically why does your example not work?
Your secondsLeft variable not connected to your JSX. So each time your component rerendered it creates a new secondsLeft variable with a value of 20 (Because rerendering is simply the execution of your function that returns JSX)
How to make your variable values persist - useState or useReducer hook for react functional component or state for class based one. So react will store all the values for you for the next rerender cycle.
Second issue is React doesn't rerender your component, it just doesn't know when it should. So what causes rerendering of your component -
Props change
State change
Context change
adding/removing your component from the DOM
Maybe I missing some other cases
So example below works fine for me
import { useEffect, useState } from "react";
function App() {
const [pause, setPause] = useState(false);
const [secondsLeft, setSecondsLeft] = useState(20);
const timer = () => {
var countdown = setInterval(() => {
if (secondsLeft <= 0) {
clearInterval(countdown);
return;
}
if (pause === true) {
clearInterval(countdown);
return;
}
setSecondsLeft((sec) => sec - 1);
}, 1000);
return () => {
clearInterval(countdown);
};
};
useEffect(timer, [secondsLeft, pause]);
const pauseTimer = () => {
setPause((pause) => !pause);
};
return (
<div>
<span>Seconds Left</span>
<p>{secondsLeft}</p>
<button onClick={pauseTimer}>{pause ? "Start" : "Pause"}</button>
</div>
);
}
import React, { useState, useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
var timer = null;
function App() {
const [counter, setCounter] = useState(0);
const [isplayin, setIsPlaying] = useState(false);
const pause = () => {
setIsPlaying(false);
clearInterval(timer);
};
const reset = () => {
setIsPlaying(false);
setCounter(0);
clearInterval(timer);
};
const play = () => {
setIsPlaying(true);
timer = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
};
return (
<div className="App">
<p>Counter</p>
<h1>{counter}</h1>
{isplayin ? (
<>
<button onClick={() => pause()}>Pause</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<>
{counter > 0 ? (
<>
<button onClick={() => play()}>Resume</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<button onClick={() => play()}>Start</button>
)}
</>
)}
</div>
);
}
export default App;
I want to start an interval by clicking on a button.
Here the interval gets started but, I can't access the value of counter. because when counter gets equal to 5, the interval should be stoped.
Here is the example:
let interval = null;
const stateReducer = (state, value) => value;
function App(props) {
const [counter, setCounter] = useReducer(stateReducer, 0);
const increment = () => {
interval = setInterval(() => {
setCounter(counter + 1);
if (counter === 5) clearInterval(interval);
console.log(counter);
}, 1000);
};
return (
<div>
<p>{counter}</p>
<button className="App" onClick={increment}>
Increment
</button>
</div>
);
}
You can run this code on codesandbox
change const stateReducer = (state, value) => value; to const
stateReducer = (state, value) => state+value;
make a variable let current_counter = 0; outside function
Change your increment function like this
current_counter = counter;
const increment = () => {
interval = setInterval(() => {
setCounter(1);
if (current_counter === 5) clearInterval(interval);
console.log(current_counter);
}, 1000);
};
Done
import React, { useReducer, useEffect, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
let interval = null;
let current_counter = 0;
const stateReducer = (state, value) => value;
function App(props) {
const [status, setStatus] = useState(false);
const [counter, setCounter] = useReducer(stateReducer, 0);
useEffect(()=>{
if(status){
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000)
return ()=>{
clearInterval(interval)
};
}
})
const increment = () => {
setStatus(!status)
};
return (
<div>
<p>{counter}</p>
<button className="App" onClick={increment}>
Increment
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Your code has a few problems.
Your reducer declaration
const initialState = { value : 0 }
const reducer = (state, action) =>{
if(action.type === 'INCREMENT') return { value : state.value + 1 }
return state
}
How you're setting your reducer
const [state, dispatch] = useReducer(reducer, initialState)
How you're dispatching your action
intervals are imperative code, you can't consistently declare an interval inside a React's handler without worrying about closure. You could use the click only to flag that the interval should start and handle all imperative code inside an useEffect. Here is a working example
const initialState = { value: 0 };
const reducer = (state, action) => {
if (action.type === "INCREMENT")
return {
value: state.value + 1
};
};
function App() {
const [state, dispatch] = React.useReducer(reducer, initialState);
const [clicked, setClicked] = React.useState(false);
useEffect(() => {
let interval = null;
if (clicked) {
interval = setInterval(() => {
dispatch({ type: "INCREMENT" });
}, 1000);
}
if (state.value > 4) clearInterval(interval);
return () => clearInterval(interval);
}, [clicked, state]);
return <button onClick={() => setClicked(true)}>{state.value}</button>;
}
If your curious about closures and how react handle imperative code take a look on this awesome article from Dan Abramov (the most detailed explanation about effects out there).