Storing non-state variables in functional components
JavascriptReactjsReact NativeReact HooksJavascript 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 oneFunctionalBar
.FunctionalBar
cannot have_foo
in the function scope because_foo
is reinitialized every time theFunctionalBar
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 onthis
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
> 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 />;
}