Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refresh control with react-query is jumping #32836

Open
AleksandrNikolaevich opened this issue Jan 6, 2022 · 10 comments
Open

Refresh control with react-query is jumping #32836

AleksandrNikolaevich opened this issue Jan 6, 2022 · 10 comments

Comments

@AleksandrNikolaevich
Copy link

AleksandrNikolaevich commented Jan 6, 2022

Description

Content inside ScrollView is jumping when I use react-query (useQuery hook) with RefreshControl.

RPReplay_Final1641463947.mov

Version

0.66.4

Output of npx react-native info

System:
    OS: macOS 12.1
    CPU: (8) x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
    Memory: 1.95 GB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 15.12.0 - /usr/local/bin/node
    Yarn: 1.22.17 - /usr/local/bin/yarn
    npm: 7.6.3 - /usr/local/bin/npm
    Watchman: 2021.11.08.00 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.11.2 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.2, iOS 15.2, macOS 12.1, tvOS 15.2, watchOS 8.3
    Android SDK:
      API Levels: 23, 24, 25, 26, 27, 28, 29, 30, 31
      Build Tools: 23.0.1, 23.0.2, 23.0.3, 25.0.0, 25.0.1, 25.0.2, 25.0.3, 26.0.2, 26.0.3, 27.0.1, 27.0.3, 28.0.2, 28.0.3, 29.0.2, 29.0.3, 30.0.2, 30.0.3
      Android NDK: Not Found
  IDEs:
    Android Studio: 2020.3 AI-203.7717.56.2031.7935034
    Xcode: 13.2.1/13C100 - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_181 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.2 => 17.0.2 
    react-native: 0.66.4 => 0.66.4 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Steps to reproduce

pull to refresh on real iOS device

Snack, code example, screenshot, or link to a repository

clone repo https://github.com/AleksandrNikolaevich/refresh-control-problem

or

App.js

import React from 'react';
import {
  RefreshControl,
  ScrollView,
  StyleSheet,
  View,
  SafeAreaView,
} from 'react-native';
import {QueryClient, QueryClientProvider, useQuery} from 'react-query';

const queryClient = new QueryClient();

const request = () => fetch('https://google.com', {method: 'GET'});

const Example = () => {
  const {isRefetching, refetch} = useQuery('example', request);

  return (
    <SafeAreaView style={styles.root}>
      <ScrollView
        refreshControl={
          <RefreshControl onRefresh={refetch} refreshing={isRefetching} />
        }>
        <View style={styles.block}>
          <View style={styles.content} />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  );
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
  },
  block: {
    height: 2000,
  },
  content: {
    height: 200,
    width: 200,
    backgroundColor: 'red',
  },
});

package.json

{
  "name": "refreshing",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "react": "17.0.2",
    "react-native": "0.66.4",
    "react-query": "^3.34.7"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/runtime": "^7.12.5",
    "@react-native-community/eslint-config": "^2.0.0",
    "babel-jest": "^26.6.3",
    "eslint": "7.14.0",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "^0.66.2",
    "react-test-renderer": "17.0.2"
  },
  "jest": {
    "preset": "react-native"
  }
}

P.S.
linked problem TanStack/query#2380

@Sourabh2001-dec
Copy link

I also came across with this issue but managed to solve with the following workaround:

// created a hook for this purpose and use a self managed state for loading
import React from 'react';

const useRefreshing = refetch => {
  const [isRefreshing, setIsRefreshing] = React.useState(false);

  const refresh = React.useCallback(async () => {
    try {
      setIsRefreshing(true);
      await refetch();
    } catch (error) {
      console.log(error);
    } finally {
      setIsRefreshing(false);
    }
  }, [refetch]);

  return [isRefreshing, refresh];
};

export default useRefreshing;

Now use above hook for refresh purpose:

const Example = () => {
  const {isRefetching, refetch} = useQuery('example', request);
  const [isRefreshing, refresh] = useRefreshing(refetch)

  return (
    <SafeAreaView style={styles.root}>
      <ScrollView
        refreshControl={
          <RefreshControl onRefresh={refresh} refreshing={isRefreshing} />
        }>
        <View style={styles.block}>
          <View style={styles.content} />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

@SEBIIWA
Copy link

SEBIIWA commented Jun 15, 2022

I came across this problem and I think the solution above me is might work because I did not test it myself, but i found this with not jumping or problem
and here is an example

let fetchWeather = function () {
  return fetch(
    `http://api.weatherapi.com/v1/current.json?key=${myapikey}&q=${query}&aqi=no`
  ).then((res) => {
    return res.json()
  })
}

const { data, refetch } = useQuery('@weather', fetchWeather)
const [refresh, setRefresh] = useState(false)

we can use the refetch function provided in the returned object in useQuery within a useCallBack hook and since refetch returns a promise we can do it like this and thanks to this you can track refresh state easily without jumping

let onRefresh = useCallback(() => {
  setRefresh(true)
  refetch().then(() => setRefresh(false))
}, [])

then finally

<AreaView
      refreshControl={
        <RefreshControl refreshing={refresh} onRefresh={onRefresh} />
      }>
         {...{something}}
</AreaView>

be aware the AreaView is a component made with styled component for ScrollView and it uses the same props that ScrollView provides

@mozzius
Copy link

mozzius commented Apr 24, 2023

I abstracted this into a hook:

export function useUserRefresh<T>(refetch: () => Promise<T>) {
  const [refreshing, setRefreshing] = useState(false);
  const handleRefresh = useCallback(() => {
    setRefreshing(true);
    refetch().then(() => setRefreshing(false));
  }, []);
  return { refreshing, handleRefresh };
}

You can then use it like this:

const query = useQuery(/* */);

const { refreshing, handleRefresh } = useUserRefresh(query.refetch);

return (
  <AreaView refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}>
    {/* do something with data */}
  </AreaView>
);

@DinoWithCurls
Copy link

I also came across with this issue but managed to solve with the following workaround:

// created a hook for this purpose and use a self managed state for loading
import React from 'react';

const useRefreshing = refetch => {
  const [isRefreshing, setIsRefreshing] = React.useState(false);

  const refresh = React.useCallback(async () => {
    try {
      setIsRefreshing(true);
      await refetch();
    } catch (error) {
      console.log(error);
    } finally {
      setIsRefreshing(false);
    }
  }, [refetch]);

  return [isRefreshing, refresh];
};

export default useRefreshing;

Now use above hook for refresh purpose:

const Example = () => {
  const {isRefetching, refetch} = useQuery('example', request);
  const [isRefreshing, refresh] = useRefreshing(refetch)

  return (
    <SafeAreaView style={styles.root}>
      <ScrollView
        refreshControl={
          <RefreshControl onRefresh={refresh} refreshing={isRefreshing} />
        }>
        <View style={styles.block}>
          <View style={styles.content} />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

This still works beautifully, but with one fix. Instead of
return [isRefreshing, refresh]
and
const [isRefreshing, refresh] = useRefreshing(refetch),
I had to use
return {isRefreshing, refresh}
and const {isRefreshing, refresh} = useRefreshing(refetch)

@bryanltobing
Copy link

Custom Hook

// useQueryRefresh.ts
export const useQueryRefresh = <T>(promiseFunctions: (() => Promise<T>)[]) => {
  const [refreshing, setRefreshing] = useState(false);

  const handleRefresh = useCallback(async () => {
    setRefreshing(true);
    await Promise.all(promiseFunctions.map((promiseFunction) => promiseFunction()));
    setRefreshing(false);
  }, [promiseFunctions]);

  return { refreshing, handleRefresh };
};

Usage

const { refreshing, handleRefresh } = useQueryRefresh([
   () => queryClient.invalidateQueries({ queryKey: ['getUser'] }),
   () => queryClient.invalidateQueries({ queryKey: ['getAccount'] }),
]);
  
<FlatList refreshing={refreshing} onRefresh={handleRefresh} />

@andresribeiro
Copy link

also happening with apollo client

@sobrinho
Copy link

I'm seeing this behavior with RTK Query as well.

@sobrinho
Copy link

While the hooks fixes the flickering, the vibration feedback is gone on iPhone when using them.

@sobrinho
Copy link

Actually looks like the haptic feedback gone is related to this other issue on tintColor: #43388

@andresribeiro
Copy link

not happening on new arch (0.76)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants