Pinky Promise
Pinky Promise

Reputation: 223

Can't perform react state in React

I'm having a problem. been browsing some questions here but seems doesn't work for me.

I'm getting this error in my three pages when I'm using the useEffect.

Warning: Can't perform a React state update on an unmounted component

This is the code of my useEffect

const UserDetailsPage = () => {


  const classes = useStyles()

  const [userData, setUserData] = useState({
    _id: "",
    name: "",
    phone: "",
    address: "",
    birthdate: "",
    gender: "",
    messenger: "",
    photo: "",
    email: "",
  })
  const [open, setOpen] = useState(false)
  const [loaded, setLoaded] = useState(false)
  const { height, width } = useWindowDimensions()

  const {
    _id,
    name,
    phone,
    address,
    photo,
    gender,
    messenger,
    birthdate,
    email,
  } = userData

  useEffect(() => {
    const user = getUser()

    getUserById("/user/" + user.userId, user.token)
      .then((data) => {
        setUserData(data)
        setLoaded(true)
      })
      .catch((error) => {
        console.log(error)
      })
  }, [])

Upvotes: 2

Views: 123

Answers (4)

Navid Nourani
Navid Nourani

Reputation: 13

Don't use async tasks in useEffect. Define an async function and call in your useEffect.

Example:

const getSTH = async() =>{
    getUserById("/user/" + user.userId, user.token)
     .then((data) => {
        if(mounted) {  // set state only when component is mounted
        setUserData(data)
        setLoaded(true)
     }
    })
    .catch((error) => {
       console.log(error)
    })

}

useEffect (()=>{
    getSTH();
},[])

I think this approach will help you.

Upvotes: -1

Izhan Yameen
Izhan Yameen

Reputation: 72

This happens when your component is unmounting before setting your state. Try this code below to check if the component is mounted or not.

useEffect(() => {
  let isMounted = true;   // add a flag to check component is mounted

  getUserById("/user/" + user.userId, user.token)
  .then((data) => {
    if(mounted) {  // set state only when component is mounted
       setUserData(data)
       setLoaded(true)
    }
  })
  .catch((error) => {
    console.log(error)
  })

  return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);

Upvotes: 1

Drew Reese
Drew Reese

Reputation: 202618

Short of getUserById returning a cancel token to cancel any inflight network requests, or an "unsubscribe" method, you can use a React ref to track if the component is still mounted or not, and not enqueue the state update if the component has already unmounted.

const isMountedRef = React.useRef(false);

useEffect(() => {
  isMountedRef.current = true;
  return () => isMountedRef.current = false;
}, []);

useEffect(() => {
  const user = getUser();

  getUserById("/user/" + user.userId, user.token)
    .then((data) => {
      if (isMountedRef.current) {
        setUserData(data);
        setLoaded(true);
      }
    })
    .catch((error) => {
      console.log(error);
    });
}, []);

Upvotes: 3

Moistbobo
Moistbobo

Reputation: 2962

This is because of the async call in useEffect finishing and then attempting to setState after the page is no longer in focus.

It can be avoided by refactoring the useEffect like so:

  useEffect(() => {
    // created a boolean to check if the component is mounted, name is arbitrary
    let mounted = true;

    const user = getUser();

    getUserById("/user/" + user.userId, user.token)
      .then((data) => {
        // only setState if mounted === true
        if (mounted) {
          setUserData(data);
          setLoaded(true);
        }
      })
      .catch((error) => {
        console.log(error);
      });

    // set mounted to false on cleanup
    return () => {
      mounted = false;
    };
  }, []);

What's different here is that I use a mounted boolean to check if the page is currently mounted. By wrapping the setState call inside an if state, I can check if it's safe to setState, therefore avoiding the error.

Additional reading

Upvotes: 1

Related Questions