How to do a useEffect() style ajax call after button press in React Native?

1.1k views Asked by At

I'm new to React and React Native. Trying to build a simple app that does an API call on the press of a button.

My code below works but just doesn't "look right" to me. useEffect() is run after the page loads? Seems to be run multiple times. I'm counting button clicks as true and then have an if statement inside useEffect. Does that mean the app is constantly checking if the button is pressed?

I tried putting useEffect() inside the getJoke() function but react native didn't like it. "Invalid hook call. Hooks can only be called inside of the body of a function component."

If I put getJoke() inside useEffect() it calls the api constantly. I jet api response after response after response.

I just want one button press to result in one API call, and the text below the button to be updated like in AJAX if this was a webpage.

import { StatusBar } from 'expo-status-bar';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Button} from 'react-native';

export default function App() {

 const [isLoading, setLoading] = useState(true);
 const [data, setData] = useState();
 const [ error, setError ] = useState(false);
 const [buttonPress, setButtonPress] = useState(false);

 const getJoke = async () => {
  try {
   const response = await fetch('https://api.chucknorris.io/jokes/random');
   const json = await response.json();
   console.log(json);
   console.log('Joke: ' + json.value);
   setData(json.value);   // Chuck API provides joke in "value" field. Only saving the joke.

  // Unfortunately you can't call hooks useEffect from here?

 } catch (error) {
   console.error(error);
   setError(true);       
 } finally {
   setLoading(false);
 }
}

// Constant api calls if I do this
 /*
 useEffect(() => {
  getJoke();
 });
 */

 useEffect(() => {
   // Check if button has been pressed, if so update the page
   if(buttonPress){
    getJoke();
    setButtonPress(false);
   }
 });

return (
  <View style={{ flex: 1, padding: 24 }}>
   <Text>Press button to get a random joke:</Text>
   <Button title="Joke" onPress={setButtonPress}/>
  
   <Text>{data}</Text>
  
  </View>
);
}
2

There are 2 answers

1
Sanka Sanjeeva On BEST ANSWER

runs after every rendering

  useEffect(() => {
    // Runs after EVERY rendering
  });

runs once after the initial rendering.

  useEffect(() => {
    // Runs ONCE after initial rendering
  }, []);

runs only when any dependency value changes.

  useEffect(() => {
    // Runs ONCE after initial rendering
    // and after every rendering ONLY IF `prop` or `state` changes
  }, [prop, state]);

So, I think you should do like this

useEffect(() => {
   // Check if button has been pressed, if so update the page
   if(buttonPress){
    getJoke();
    setButtonPress(false);
   }
}, [buttonPress]);

And the reason for the Invalid hook call is there are few rules.

Don’t call Hooks inside loops, conditions, or nested functions

1
Isaac On

Remove all your useEffect block and replace with useEffect below

useEffect(() => {
   // Check if button has been pressed, if so update the page
   if(buttonPress){
    getJoke();
    setButtonPress(false);
   }
}, [buttonPress]);

Basically how useEffect works is that, by introducing second parameter, we are saying, run useEffect only when value of buttonPress has changed.