John Doah
John Doah

Reputation: 1999

Executing function in child component from parent

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

Answers (3)

Onlinogame
Onlinogame

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

Ren&#233; Link
Ren&#233; Link

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

wowandy
wowandy

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

Related Questions