简体   繁体   中英

React useState is reset to undefined and I have no idea why

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.

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>

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)

    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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM