lana
lana

Reputation: 13

React-Native Making setInterval Declarative with React Hooks

I'm playing with React Hooks for more than a few hours, I'm probably ran into an intriguing problem: using setInterval just doesn’t work as I'd expect with react-native

function Counter() {
  const [time, setTime] = useState(0);
  const r = useRef(null);
  r.current = { time, setTime };
  useEffect(() => {
    const id = setInterval(() => {
      console.log("called");
      r.current.setTime(r.current.time + 1);
    }, 1000);
    return () => {
      console.log("cleared");
      clearInterval(id);
    };
  }, [time]);

  return <Text>{time}</Text>;
}

The code above should clearInterval every time that time state changes It works fine on ReactJS but on React-native I'm getting an error says "Callback() it's not a function"

enter image description here

It's working as expected in Reactjs

https://codesandbox.io/s/z69z66kjyx

"dependencies": {
    "react": "16.8.3",
    "react-native": "^0.59.6",
    ...}

Update: I tried to use the ref like this example but still getting same error

const [time, setTime] = useState(0);
        useInterval(() => {
            setTime(time +1);
        });

        return (<Text>{time}</Text>);
}

function useInterval(callback) {
    const savedCallback = useRef();

    // Remember the latest function.
    useEffect(() => {
        savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useEffect(() => {
            let id = setInterval(()=>savedCallback.current(), delay);
            return () => clearInterval(id);
    });
}

Upvotes: 1

Views: 933

Answers (2)

Liam
Liam

Reputation: 6743

Because you are mutating the DOM via a DOM node refs and the DOM mutation will change the appearance of the DOM node between the time that's rendered and your effects mutates it. then you don't need to use useEffect you will want to use useLayoutEffect

useLayoutEffect this runs synchronously immediately after React has performed all the DOM mutations.

import React, {useState, useLayoutEffect,useRef} from 'react';
import { Text} from 'react-native';

const [time, setTime] = useState(0);
        useInterval(() => {
            setTime(time +1);
        });

        return (<Text>{time}</Text>);
}

function useInterval(callback) {
    const savedCallback = useRef();

    // Remember the latest function.
    useLayoutEffect(() => {
        savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useLayoutEffect(() => {
            let id = setInterval(()=>{
              console.log('called');
              return savedCallback.current();
            }, delay);
            return () => {
                console.log('cleared');
                return clearInterval(id);
            }
    });
}

if you just using useEffect and getting this error

Uncaught TypeError: callback is not a function at flushFirstCallback (scheduler.development.js:348) at flushWork (scheduler.development.js:441) at MessagePort.channel.port1.onmessage (scheduler.development.js:188)

This is a bug in RN because of wrong scheduler version, Unfortunately RN didn't have an explicit dependency on scheduler version by mistak. Dan Abramov already fixed this bug on scheduler version "0.14.0"

To solve the problem just run the following command

npm install [email protected] --save

Or Try adding "scheduler": "0.14.0" to your package.json in dependencies and re-running your package manager

Upvotes: 2

Drew Reese
Drew Reese

Reputation: 202686

You should be able to still use the state hook variables in the effect hook as they are in scope.

useRef: mutations here aren't tracked, so they don't trigger re-renders.

CodeSandbox Counter Example

I feel that using refs in the way you're trying to is more verbose than just using the state and setter directly. The useRef ref is intended for mutable values over time, but you already get that with the useState hook. The ref works because you're not really mutating the ref, but instead are simply overwriting it each render cycle with the contents of the useState hook that are updated.

I've updated my sandbox to use useRef as the way you have, your useEffect hook was causing your cleanup function to fire on every render, so removed the dependency. You'll notice now you only see "called" until you refresh.

Upvotes: 0

Related Questions