Storing non-state variables in functional components

41,375

Solution 1

The useRef hook is not just for DOM refs, but can store any mutable value you like.

Example

function FunctionalBar(props) {
  const [foo] = useState(new Animated.Value(0));
  const _foo = useRef(0);

  function showFoo() {
    let anim = Animated.timing(foo, { toValue: 1, duration: 1000, useNativeDriver: true });
    anim.start(() => console.log(_foo.current));
  }

  useEffect(() => {
    function _onChangeFoo({ value }) {
      _foo.current = value;
    }

    foo.addListener(_onChangeFoo);
    showFoo();
    return () => foo.removeListener(_onChangeFoo);
  }, []);

  return <View />;
}

Solution 2

You can use useRef hook (it's the recommended way stated in docs):

  • Declaring variable: const a = useRef(5) // 5 is initial value
  • getting the value: a.current
  • setting the value: a.current = my_value

Solution 3

Just to support Tholle answer here is the official documentation

Reference

However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

Solution 4

This is a pretty unusual example, but if I'm reading this correctly, you simply want to store unique _foo objects everytime the component mounts and destroy them when it unmounts, but also prevent extra rerenders when this value changes.

I have run into this scenario before and simple object (map / hash) should do the trick:

let foos = {}
let fooCount = 0

function F(props) {
  useEffect(() => {
    let fooId = fooCount++
    foos[fooId] = new Animated.Value(0)
    foos[fooId].addListener(...)
    return () => foos[fooId].removeListener(...)
  }, []) // <-- do not rerun when called again (only when unmounted)

  ...render...
}

or something to that effect. if you have a runnable example could tweak it to make it fit your example better. either way, most things with scope problems are solved with primitives.

Solution 5

I've had some luck using the useRef hook with destructuring (+ an optional variable alias "my"), and then you keep all your values in the my object so you don't have to use multiple refs or keep using myref.current all the time:

function MyComponent(props) {
  const componentRef = useRef({});
  const { current: my } = componentRef;

  my.count = 42;
  console.log(my.count); // 42

  my.greet = "hello";
  console.log(my.greet); // hello

  return <div />;
}
Share:
41,375
woodpav
Author by

woodpav

Updated on July 30, 2021

Comments

  • woodpav
    woodpav almost 3 years

    Below are two React Components that do almost the same thing. One is a function; the other is a class. Each Component has an Animated.Value with an async listener that updates _foo on change. I need to be able to access _foo in the functional component like I do with this._foo in the classical component.

    • FunctionalBar should not have _foo in the global scope in case there are more than one FunctionalBar.
    • FunctionalBar cannot have _foo in the function scope because _foo is reinitialized every time the FunctionalBar renders. _foo also should not be in state because the component does not need to render when _foo changes.
    • ClassBar does not have this problem because it keeps _foo initialized on this throughout the entire life of the Component.

    How do I keep _foo initialized throughout the life of FunctionalBar without putting it in the global scope?

    Functional Implementation

    import React from 'react';
    import { Animated, View } from 'react-native';
    
    var _foo = 0;
    
    function FunctionalBar(props) {
    
      const foo = new Animated.Value(0);
    
      _onChangeFoo({ value }) {
        _foo = value;
      }
    
      function showFoo() {
        let anim = Animated.timing(foo, { toValue: 1, duration: 1000, useNativeDriver: true });
        anim.start(() => console.log(_foo));
      }
    
      useEffect(() => {
        foo.addListener(_onChangeFoo);
        showFoo();
        return () => foo.removeListener(_onChangeFoo);   
      });
    
      return <View />;
    
    }
    

    Classical Implementation

    import React from 'react';
    import { Animated, View } from 'react-native';
    
    class ClassBar extends React.Component {
    
      constructor(props) {
        super(props);
        this.state = { foo: new Animated.Value(0) };
        this._foo = 0;
        this._onChangeFoo = this._onChangeFoo.bind(this);
      }
    
      componentDidMount() {
        this.state.foo.addListener(this._onChangeFoo);
        this.showFoo();
      }
    
      componentWillUnmount() {
        this.state.foo.removeListener(this._onChangeFoo);
      }
    
      showFoo() {
        let anim = Animated.timing(this.state.foo, { toValue: 1, duration: 1000, useNativeDriver: true });
        anim.start(() => console.log(this._foo));
      }
    
      _onChangeFoo({ value }) {
        this._foo = value;
      }
    
      render() {
        return <View />;
      }
    
    }
    
  • vikrant
    vikrant about 4 years
    what is the difference between decraling foos outside of function F, vs inside?
  • azium
    azium about 4 years
    @vikrant component functions are called everytime something renders, so if you put normal variable declarations inside then they get overwritten on every render.
  • Scribblemacher
    Scribblemacher almost 4 years
    Why does it work this way? In non-React functions, you can directly set _foo (provided you change it from const to let) inside a function so long as it is still in scope. What's happening here that makes this different?
  • gaurav5430
    gaurav5430 about 3 years
    useRef returns an object whose reference would not change across re-renders, the actual value for foo is then kept in the current property of that object. @Scribblemacher
  • phocks
    phocks almost 3 years
    This will be fine if you only have 1 instance of the component on the page at any one time. But if you have multiple components the variables declared outside the component function will be used for all the components. I've found it's best to use the useRef method.