denistepp
denistepp

Reputation: 530

Update Child Component Data on Parent Refresh React Native

I am fairly new to React Native and currently try to implement pull-to-refresh functionality in my app. Here is my Parent component snippet:

function MainScreenBoss({ navigation }) {
    const [refreshing, setRefreshing] = useState(false);

    //here I'm trying to refresh (example from docs)
    const onRefresh = React.useCallback(async () => {
        setRefreshing(true);

        wait(2000).then(setRefreshing(false));
    }, [refreshing]);

    return (
        <ScrollView
            contentContainerStyle={styles.containerScroll}
            refreshControl={
                <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
            }>
            <View style={styles.container}>
                //other components here
                <View style={styles.containerWidget}>
                    //need to refresh this component
                    <Widget style={styles.widget} />
                </View>
            </View>
        </ScrollView>
    );
}

I am trying to refresh Widget component as it has the call to API url in it with loading animation. Here is the snipped of Widget component:

function Widget() {
    const [isLoading, setLoading] = useState(true);
    const [data, setData] = useState([]);

    //call to api returns data
    useEffect(() => {
        getData(API_GET_WIDGET_DATA, setLoading, setData);
    }, []);

    return (
        <View style={styles.container}>
            {isLoading ? (
                <ActivityIndicator
                    loader="blue"
                    visible={isLoading}
                    style={styles.animation}
                />
            ) : (
                <>
                    <View style={styles.Contents}>
                    //need to refresh data is it used in here when loading is finished
                    </View>
                </>
            )}
        </View>
    );
}

I guess I need to force update the widget component or launch the loading function again somehow, but I do not quite understand what should I do.

Edit: the api function looks like this:

export function getData(reqOptions, setLoading, setData) {
    fetch(apiURL, reqOptions)
        .then((response) => response.json())
        .then((json) => setData(json.data))
        .catch((error) => console.error(error))
        .finally(() => setLoading(false));
}

Upvotes: 1

Views: 4698

Answers (1)

Konstantin
Konstantin

Reputation: 1458

If I understand well, in order to update your widget, you gotta re-do the fetch that you have inside your useEffect.

The useEffect you currently have only executes on mount of the component, as the dep. array is empty. From the looks of your parent component, the Widget does not get unmounted, therefore your useEffect is only called once. What you have to do is to take your refreshing state and pass it to the Widget, so that it knows when it has to refetch the data.

Try something like this:

function MainScreenBoss({ navigation }) {
    const [refreshing, setRefreshing] = useState(false);

    //here I'm trying to refresh (example from docs)
    const onRefresh = React.useCallback(async () => {
        setRefreshing(true);

        wait(2000).then(setRefreshing(false));
    }, [refreshing]);

    return (
        <ScrollView
            contentContainerStyle={styles.containerScroll}
            refreshControl={
                <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
            }>
            <View style={styles.container}>
                //other components here
                <View style={styles.containerWidget}>
                    //need to refresh this component
                    <Widget style={styles.widget} refreshing={refreshing} /> // pass this prop
                </View>
            </View>
        </ScrollView>
    );
}

Widget:

function Widget(props) {
    const [isLoading, setLoading] = useState(true);
    const [data, setData] = useState([]);

    //call to api returns data

   useEffect(() => {
        getData(API_GET_WIDGET_DATA, setLoading, setData);
    }, []);

    useEffect(() => {
      if (props.refreshing) {
        getData(API_GET_WIDGET_DATA, setLoading, setData);
      }
    }, [props.refreshing]); // on every refreshing `true` state

    return (
        <View style={styles.container}>
            {isLoading ? (
                <ActivityIndicator
                    loader="blue"
                    visible={isLoading}
                    style={styles.animation}
                />
            ) : (
                <>
                    <View style={styles.Contents}>
                    //need to refresh data is it used in here when loading is finished
                    </View>
                </>
            )}
        </View>
    );
}

Edit: You can either modify the existing useEffect to include the check of the data, or else create another one which runs only on mount, with another useEffect running on update state. I've added the second case.

Edit 2:

  1. There is nothing wrong with that, actually that is better because the refreshing is handled at a correct time, when the call is finished and the data is fulfilled. With your current config the call may not be finished, but after 2 seconds you're setting refreshing to false, this may bring problems.

  2. The beauty of hooks is that you can use as many of them as you want, as compared to the class methods of React before hooks introduction. So there is no problem with that, but you can actually change it around and have something like:

    useEffect(() => {
      if (!data || props.refreshing) {
        getData(API_GET_WIDGET_DATA, setLoading, setData);
      }
    }, [data, props.refreshing]);
    
  3. If you may, one last thing: I would not pass state setters to your fetch fn, I would handle the state updates in the component and just make sure that the fn returns your data. It's just for separation of concerns causes.

Upvotes: 1

Related Questions