Storing non-state variables in functional components

JavascriptReactjsReact NativeReact Hooks

Javascript Problem Overview


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 />;
  }

}

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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 />;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwoodpavView Question on Stackoverflow
Solution 1 - JavascriptTholleView Answer on Stackoverflow
Solution 2 - JavascriptyayaView Answer on Stackoverflow
Solution 3 - Javascriptm-farhanView Answer on Stackoverflow
Solution 4 - JavascriptaziumView Answer on Stackoverflow
Solution 5 - JavascriptphocksView Answer on Stackoverflow