Storing non-state variables in functional components
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
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 betweenuseRef()
and creating a{current: ...}
object yourself is thatuseRef
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 />;
}
woodpav
Updated on July 30, 2021Comments
-
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 withthis._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 ofFunctionalBar
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 about 4 yearswhat is the difference between decraling
foos
outside of function F, vs inside? -
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 almost 4 yearsWhy does it work this way? In non-React functions, you can directly set _foo (provided you change it from
const
tolet
) inside a function so long as it is still in scope. What's happening here that makes this different? -
gaurav5430 about 3 yearsuseRef 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 almost 3 yearsThis 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.