Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native hooks, useRef and useEffect

I'm trying to translate a Class Component into a Functional one with React Native.

My Search component lets the user search for a film name and I'm making an API call to show him all corresponding films.

Here is my class component :

class Search extends React.Component {

    constructor(props) {
        super(props);
        this.searchedText = "";
        this.page = 0
        this.totalPages = 0
        this.state = {
            films: [],
            isLoading: false
        }
    }

    _loadFilms() {
        if (this.searchedText.length > 0) {
            this.setState({ isLoading: true })
            getFilmsFromApiWithSearchedText(this.searchedText, this.page+1).then(data => {
                this.page = data.page
                this.totalPages = data.total_pages
                this.setState({
                    films: [ ...this.state.films, ...data.results ],
                    isLoading: false
                })
            })
        }
    }

    _searchTextInputChanged(text) {
        this.searchedText = text
    }

    _searchFilms() {
        this.page = 0
        this.totalPages = 0
        this.setState({
            films: [],
        }, () => {
            this._loadFilms()
        })
    }

render() {
        return (
            <View style={styles.main_container}>
                <TextInput
                    style={styles.textinput}
                    placeholder='Titre du film'
                    onChangeText={(text) => this._searchTextInputChanged(text)}
                    onSubmitEditing={() => this._searchFilms()}
                />
                <Button title='Rechercher' onPress={() => this._searchFilms()}/>
                <FlatList
                    data={this.state.films}
                    keyExtractor={(item) => item.id.toString()}
                    renderItem={({item}) => <FilmItem film={item}/>}
                    onEndReachedThreshold={0.5}
                    onEndReached={() => {
                        if (this.page < this.totalPages) {
                            this._loadFilms()
                        }
                    }}
                />
            </View>
        )
    }
}
render() {
      return (
            <View>
                <TextInput
                    onChangeText={(text) => this._searchTextInputChanged(text)}
                    onSubmitEditing={() => this._searchFilms()}
                />
                <Button title='Search' onPress={() => this._searchFilms()}/>
                <FlatList
                    data={this.state.films}
                    keyExtractor={(item) => item.id.toString()}
                    renderItem={({item}) => <FilmItem film={item}/>}
                    onEndReachedThreshold={0.5}
                    onEndReached={() => {
                        if (this.page < this.totalPages) {
                            this._loadFilms()
                        }
                    }}
                />
                {this._displayLoading()}
            </View>
        )
    }
}

How can I translate the following with hooks :

  • this.page and this.totalPages ? is useRef the solution ?

  • in _searchFilms() I'm using setState callback to make a new API call when my film list is empty (because it's a new search). But doing it right after doesn't work because setState is asynchronous. But I can't find a way to do it with hooks.

I think useEffect could do this but :

  • I only want to make this API call when my film list is empty, because I call _searchFilms() for a new search.
  • _loadFilms() is called on user scroll to add more films to the FlatList (for the same search) so I can't clear this.films in this case.

Here is how I translated it so far :

const Search = () => {
    const [searchText, setSearchText] = useState('');
    const [films, setFilms] = useState([]);
    // handle pagination
    const page = useRef(0);
    const totalPages = useRef(0);
    // handle api fetch
    const [isLoading, setIsLoading] = useState(false);


    const loadFilmsFromApi = () => {
        getFilmsFromApiWithSearchedText(searchText, page + 1).then((data) => {
            page.current = data.page;
            totalPages.current = data.total_pages;
            setFilms(films => [...films, ...data.results]);
            setIsLoading(false);
        })
    };

    const searchFilm = () => {
        if (searchText.length > 0) {
            page.current = 0;
            totalPages.current = 0;
            setFilms([]);
            // HERE MY Films list won't be cleared (setState asynchronous)
            loadFilmsFromApi();

            // after the api call, clear input
            setSearchText('');
        }
    };

    useEffect(() => {
        console.log(page, totalPages, "Film number" + films.length);
    }, [films]);

like image 433
A. Ecrubit Avatar asked Mar 18 '26 10:03

A. Ecrubit


1 Answers

I think you are on the right path. As for totalPages and page, having it as a ref makes sense if you want to maintain that values between different renders ( when setting state )

const Search = () => {

  const [searchText, setSearchText] = useState('');
  const [films, setFilms] = useState([]);
  // handle pagination
  const page = useRef(0);
  const totalPages = useRef(0);
  // handle api fetch
  const [isLoading, setIsLoading] = useState(false);
  
  // This can be invoked by either search or user scroll
  // When pageNum is undefined, it means it is triggered by search
  const loadFilmsFromApi = (pageNum) => {
    console.log("APPEL", 'loadFills');
    getFilmsFromApiWithSearchedText(searchText, pageNum ? pageNum + 1 : 1).then((data) => {
      page.current = data.page;
      totalPages.current = data.total_pages;
      
      setFilms(films => {
        if(pageNum) {
          return [...films, ...data.results];
        } else {
          return [data.results];
        }
      });
      
      setIsLoading(false);
    })
  };
  
  useEffect(() => {
   if (searchText.length > 0) {
      page.current = 0;
      totalPages.current = 0;
      
      setFilms([]);
      loadFilmsFromApi();
      // after the api call, clear input
      setSearchText('');
    }
  
  }, [searchText, loadFilmsFromApi]);


  useEffect(() => {
    console.log(page, totalPages, "Nombre de film " + films.length);
  }, [films]);

  return ( <
    div > Search < /div>
  );

};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
like image 94
Sushanth -- Avatar answered Mar 20 '26 06:03

Sushanth --



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!