Reputation: 23
I am still very new to react and I ran into the following problem:
I created a component which will contains a watchPosition method and an interval. This component will get re-rendered very often but I don't want the interval and the watchPosition method to get re-created every time. My solution is to store the ids of the interval and watchPosition in useState so that a new interval/watchPosition only gets created if the states are undefined. This is almost working :D
This is my code (I added some console outputs to see what's happening):
import { useState } from "react";
import "./styles.css";
export default function App() {
console.log("NEW RENDER");
const [watcherId, setWatcherId] = useState();
const [updatingInterval, setUpdatingInterval] = useState();
//test is a dummy variable to manually create re-rendering and check if new intervals get created
const [test, setTest] = useState(1);
async function initializeContext() {
console.log("current value of updatingInterval: ", updatingInterval);
if (updatingInterval === undefined) {
console.log("new interval created");
setUpdatingInterval(setInterval(updateValues, 1000));
}
console.log("current value of watcherId: ", watcherId);
if (watcherId === undefined) {
console.log("new geoWatcher created");
setWatcherId(navigator.geolocation.watchPosition(success, error, {
maximumAge: 500,
enableHighAccuracy: true
}));
}
}
function success() {
console.log("executed!");
}
function error(error) {
console.error("error: ", error);
}
function buttonFunction() {
setTest((prevText) => {
return prevText + 1;
});
}
function updateValues() {
console.log("Interval executed");
}
initializeContext();
return (
<div className="App">
<h1>{test}</h1>
<button onClick={buttonFunction}>Switch</button>
</div>
);
}
This is the corresponding console log:
As you can see for some reason the values of the two states are set to 16 and 1 and in the next execution they are undefined again. After that they are set to 25 and 2 and from this time on everything works as expected.
I have no idea what I did wrong.
Upvotes: 1
Views: 964
Reputation: 15520
You should not call states directly into the rendering function. That may cause infinite re-renderings. For example, you update states > related components get re-rendered > states will be updated again (because state update will be called in the rendering again).
According to your logic, seemingly, you want to update states only once after the component App
gets rendered. In that case, I'd suggest you use useEffect
instead.
Furthermore, you should use useRef
for the interval and watcherId. That would help you to reduce useless renderings.
import { useState, useEffect, useRef } from "react";
import "./styles.css";
export default function App() {
console.log("NEW RENDER");
const watcherIdRef = useRef();
const updatingIntervalRef = useRef();
//test is a dummy variable to manually create re-rendering and check if new intervals get created
const [test, setTest] = useState(1);
//only called in the first rendering
useEffect(() => {
initializeContext();
}, [])
function initializeContext() {
//clean up the interval
if(updatingIntervalRef.current) {
clearInterval(updatingIntervalRef.current)
}
//no need to have undefined check
updatingIntervalRef.current = setInterval(updateValues, 1000);
watcherIdRef.current = navigator.geolocation.watchPosition(success, error, {
maximumAge: 500,
enableHighAccuracy: true
});
}
function success() {
console.log("executed!");
}
function error(error) {
console.error("error: ", error);
}
function buttonFunction() {
setTest((prevText) => {
return prevText + 1;
});
}
function updateValues() {
console.log("Interval executed");
}
return (
<div className="App">
<h1>{test}</h1>
<button onClick={buttonFunction}>Switch</button>
</div>
);
}
Testable version
const { useState, useEffect, useRef } = React;
function App() {
console.log("NEW RENDER");
const watcherIdRef = useRef();
const updatingIntervalRef = useRef();
//test is a dummy variable to manually create re-rendering and check if new intervals get created
const [test, setTest] = useState(1);
//only called in the first rendering
useEffect(() => {
initializeContext();
}, []);
function initializeContext() {
console.log("current value of updatingInterval: ", updatingIntervalRef.current);
//clean up the interval
if(updatingIntervalRef.current) {
clearInterval(updatingIntervalRef.current)
}
//no need to have undefined check
updatingIntervalRef.current = setInterval(updateValues, 1000);
watcherIdRef.current = navigator.geolocation.watchPosition(success, error, {
maximumAge: 500,
enableHighAccuracy: true
});
}
function success() {
console.log("executed!");
}
function error(error) {
console.error("error: ", error);
}
function buttonFunction() {
setTest((prevText) => {
return prevText + 1;
});
}
function updateValues() {
console.log("Interval executed");
}
return (
<div className="App">
<h1>{test}</h1>
<button onClick={buttonFunction}>Switch</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>
Upvotes: 1
Reputation: 26
I think you should use useEffect and provide a blank dependency array for calling initializeContext
import { useState ,useEffect} from "react";
import "./styles.css";
export default function App() {
console.log("NEW RENDER");
const [watcherId, setWatcherId] = useState();
const [updatingInterval, setUpdatingInterval] = useState();
//test is a dummy variable to manually create re-rendering and check if new intervals get created
const [test, setTest] = useState(1);
async function initializeContext() {
console.log("current value of updatingInterval: ", updatingInterval);
if (updatingInterval === undefined) {
console.log("new interval created");
setUpdatingInterval(setInterval(updateValues, 1000));
}
console.log("current value of watcherId: ", watcherId);
if (watcherId === undefined) {
console.log("new geoWatcher created");
setWatcherId(navigator.geolocation.watchPosition(success, error, {
maximumAge: 500,
enableHighAccuracy: true
}));
}
}
function success() {
console.log("executed!");
}
function error(error) {
console.error("error: ", error);
}
function buttonFunction() {
setTest((prevText) => {
return prevText + 1;
});
}
function updateValues() {
console.log("Interval executed");
}
useEffect(()=>{
initializeContext();
},[])
return (
<div className="App">
<h1>{test}</h1>
<button onClick={buttonFunction}>Switch</button>
</div>
);
}
//If any issues still Please ask.
Upvotes: 0
Reputation: 860
remove initializeContext() from function ,You should call this in useEffect
useEffect(()=>{
initializeContext();
}, [])
Not in function component. When it prints values it is when the setIterval is executing the function, and it prints undefined when the function is not called (meanwhile)
Upvotes: 0