Reputation: 1999
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>
Upvotes: 2
Views: 1013
Reputation: 1023
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>
)
Upvotes: 3
Reputation: 51483
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.
Upvotes: 3
Reputation: 1312
You can define Animated.value
in the parent component and put it in the props of child component:
const Parent = () => {
const translationY = useRef(new Animated.Value(-120)).current;
const displayPopup = useCallback(() => {
Animated.spring(translationY, {
toValue: 100,
useNativeDriver: true,
}).start();
}, [translationY]);
return (
<SafeAreaView>
<Popup translationY={translationY}/>
<Button onPress={displayPopup}/>
</SafeAreaView>
);
};
const Popup = ({translationY}) => {
const opacity = useRef(new Animated.Value(1)).current;
const {theme} = useContext(ThemeContext);
return (
<Animated.View
style={[
styles.container,
{
backgroundColor: theme.popup,
opacity: opacity,
transform: [{translateY: translationY}],
},
]}>
...
</Animated.View>
);
};
Upvotes: 2