I'm using React Native, and I have a functional component that has a function in it. This child component is located inside another component, on that another component I have a button. So, When the user clicks the button, I want to execute the function in the child component.
I read about forwardRef (as I saw few questions about this that suggested this solution): https://reactjs.org/docs/forwarding-refs.html But it does not seems to fit my problem. I don't need to access an element in my child component, only to execute the function.
This is my child component:
const Popup = () => {
const opacity = useRef(new Animated.Value(1)).current;
const translationY = useRef(new Animated.Value(-120)).current;
const {theme} = useContext(ThemeContext);
const displayPopup = () => {
Animated.spring(translationY, {
toValue: 100,
useNativeDriver: true,
}).start();
};
return (
<Animated.View
style={[
styles.container,
{
backgroundColor: theme.popup,
opacity: opacity,
transform: [{translateY: translationY}],
},
]}>
...
</Animated.View>
);
};
Inside the parent component:
<SafeAreaView>
// Other components
<Popup />
<Button onPress={() => {//NEEDS TO CALL displayPopup}}/>
</SafeAreaView>
Using a forward ref to give access to a child function can be a solution, but it's not the React way to do it (See useImperativeHandle hook)
Instead, I would change a props of the Popup component to trigger the animation you want to use:
import { useEffect } from "react";
const Popup = ({display = false}) => {
const opacity = useRef(new Animated.Value(1)).current;
const translationY = useRef(new Animated.Value(-120)).current;
const {theme} = useContext(ThemeContext);
useEffect(() => {
if(display){
Animated.spring(translationY, {
toValue: 100,
useNativeDriver: true,
}).start();
}
}, [display])
return (
<Animated.View
style={[
styles.container,
{
backgroundColor: theme.popup,
opacity: opacity,
transform: [{translateY: translationY}],
},
]}>
...
</Animated.View>
);
};
Parent component:
const [display, setDisplay] = useState(false);
return(
<SafeAreaView>
// Other components
<Popup display={display} />
<Button onPress={() => {setDisplay(true)}}/>
</SafeAreaView>
)
You can use a ref forward it to the child component and use useImparativeHandle in the child to augment the ref so that the parent can invoke it.
Just run this code snippet:
const { useState, useRef, createRef, forwardRef, useImperativeHandle } = React;
const TextInput = forwardRef(({value, otherProps}, ref) => {
const [text, setText] = useState(value || "");
const clearText = () => setText("");
// augment the ref by adding the clearText function
useImperativeHandle(ref, () => ({
clearText
}));
return (
<input ref={ref} type="text" onChange={(e) => setText(e.target.value)}
value={text} {...otherProps}
/>
);
});
const Parent = () => {
const textRef = createRef();
const onClickButton = () => textRef.current.clearText();
return (
<div>
<TextInput ref={textRef} value="Enter Text" />
<button onClick={() => onClickButton()}>Clear Text</button>
</div>
);
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
As the React docs states
As always, imperative code using refs should be avoided in most cases.
and later they say
In this example, a parent component that renders would be able to call inputRef.current.focus().
It's up to you to decide if your code is one of the few cases in which it makes sense.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With