0

I'm building an app with React native. I'm using a FlatList with an onRefresh handler:

 <FlatList 
  data={data} 
  renderItem={renderPost}
  keyExtractor={(item, index) => index.toString()}
  onEndReached={handleLoadMore}
  onEndReachedThreshold={0.5}
  ListFooterComponent={renderFooter}
  refreshing={isRefreshing}
  onRefresh={handleRefresh}>
</FlatList>

Within that onRefresh handler I reset the data list and fetch new data:

 const handleRefresh = () => {
    setData([]);
    setIsRefreshing(true);
    fetchData();
  }

The problem is that data is never set to []. I can read here that it's expected behaviour: useState set method not reflecting change immediately.

But what would be a better way? Because when I use, useEffect I have the same problem:

useEffect(() => {
  setData([])
  fetchData();
}, [isRefreshing]);

const handleRefresh = () => {
    setIsRefreshing(true);
  }

isRefreshing is never set to true.

What is the best way of tackling this?

--EDIT

fethData method:

const fetchData = () => {
  const url = 'my-api-url.com?page=' + page;

  fetch(url, {
    method: 'GET',
  }).then((response) => response.json())
    .then((json) => {        
      setData(data.concat(json.data));
      setIsLoading(false);
      setIsRefreshing(false);
    });
}
Jamie
  • 10,302
  • 32
  • 103
  • 186
  • when you `refresh`, `handleRefresh` gets called and sets `data` and `isRefreshing`. check if `handleRefresh` is called on `refresh` – Prakash Reddy Potlapadu Mar 11 '20 at 15:24
  • Hi, Yes it's called. I've checked that multiple times. – Jamie Mar 11 '20 at 15:27
  • can i see the code for fetchData? – Lelouch Mar 11 '20 at 15:30
  • @Lelouch sure please check my edit – Jamie Mar 11 '20 at 15:33
  • It sounds like all of these things are related to one another, so what I'd do is to have a reducer that handles all related state together. Alternatively, you could have `useEffect`s that track all pieces of `state` and act accordingly, but that'd be too much IMHO. – goto Mar 11 '20 at 15:40
  • You can `useEffect` for both `isRefreshing` and `data` and inside the callback, call `fetchData` when `isRefreshing && !data.length`. – Titus Mar 11 '20 at 15:40

1 Answers1

1

If you get what I'm trying to do here it might work best for you

// how about isolating all the data fetch related hooks
// fetch will be called anytime your request params updates
// qs is from query string library

const useDataFetch = (url, method, params) => {
  const [refreshing, setRefreshing] = useState(false)
  const [fetching, setFetching] = useState(false)
  const [data, setData] = useState([])

  useEffect(() => {
    async (() => {
      const url = `${url}?${qs.stringify(params)}`

      // we set fetching to true while data is still to be fetched
      await setFetching(true)
      const rawResponse = await fetch(url, {method})

      // and set it back to false when done
      const newData = rawResponse.json().data

      if (refreshing) {
        setData(newData)
        setRefreshing(false)
      } else {
        setData([...data, ...newData])
      }

      setFetching(false)
    })()
  }, [params])

  return {refreshing, setRefreshing, fetching, data}
}

// and use it like this
// only params is outside of useDataFetch because of the feature refreshing
export default myApp = () => {
  const [params, setParams] = useState({page: 1})
  const {refreshing, setRefreshing, fetching, data} = useDataFetch('my-api-url.com', 'GET', params)

  const handleRefresh = async () => {
    await setRefreshing(true)
    setParams({page: 1})
  }

  return (
    <FlatList 
      data={data} 
      renderItem={renderPost}
      keyExtractor={(item, index) => index.toString()}
      onEndReached={handleLoadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={renderFooter}
      refreshing={refreshing}
      onRefresh={handleRefresh}>
    </FlatList>
  )
}

// now things are reuseable and less code from now on
Lelouch
  • 910
  • 6
  • 19
  • hey! thanks, the thing is that `only` on refresh the data should be entirely replaced. In other cases (infinite loop) it should `concat` the data (like your example). How can I combine this? – Jamie Mar 11 '20 at 16:12
  • Thanks a lot Lelouch! – Jamie Mar 11 '20 at 20:09
  • Any idea why I get `can not use keyword 'await' outside an async function` when I try your custom hook ? @Lelouch – Jamie Mar 18 '20 at 16:00
  • because async and await comes hand in hand when waiting for asynchronous functions – Lelouch Mar 19 '20 at 07:50