Storing non-state variables in functional components

woodpav picture woodpav · Nov 5, 2018 · Viewed 16.7k times · Source

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

}

Answer

Tholle picture Tholle · Nov 5, 2018

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