Storing non-state variables in functional components

3216 views javascript
1

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.

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

}

answered question

Have you tried const foo = useState(new Animated.Value(0));?

That doesn't address the problem because I still need to attach the listener. This is a scope question.

I'm still not sure what you are trying to achieve. Sure, logging something on the instance obviously won't work in a function component, but what do you want to do with _foo?

Make an imperative call in a useEffect.

I could have a global object with all every _foo and clean it up on unmount. It feels like there needs to be another hook (or a scope passed to useEffect).

1 Answer

11

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(_onChangeFoo)
    return () => foos[fooId].removeListener(_onChangeFoo)
  }, []) // <-- do not change on rerenders

  ...render...
}

posted this

Have an answer?

JD

Please login first before posting an answer.