Undefined is not an object, how do I access object refreshed by hooks after it is loaded

484 views Asked by At

In my app I type in a string, which is then immediately invoked in rest api call and json object is returned containing all drinks according to the name typed in. Then I would like to display a specific part of that object, for example the name, but I get undefined error when accesssing that part of the object, which I will elaborate later. I am implementing react-navigation and react-elements and variables are refreshed by hooks.

export default class App extends Component {
    render() {    
        console.log('change')
        return (
            <NavigationContainer>
                <Drawer.Navigator initialRouteName="Home">
                    <Drawer.Screen name="Home" component={HomeScreen} />
                    <Drawer.Screen name="Notifications" component={NotificationsScreen} />
                </Drawer.Navigator>
            </NavigationContainer>   
        );
    }
}

The problem is in Home, my code works as far as displaying full object after each typed character: I can display object of all the drinks containing queried name that I typed in. But then, I want to display a specific element in the object, for example the first id (object.drinks[0].idDrink), which I assume doesnt load up yet and there comes the undefined error. I could access the id before when doing things without hooks, that part is not wrong. I assume I broke things implementing many hooks that are refreshing my app.

function HomeScreen() {
  
    const [query, setQuery] = useState('');
    const [jsonData, setJsonData] = useState([]);
    const [loading, setLoading] = useState(true);
    console.log(loading, '1') 
    useEffect(() => {
        setLoading(true);
        console.log(loading, '2') 

        fetch('https://www.thecocktaildb.com/api/json/v1/1/search.php?s=' + query, {
            method: 'GET',
        })
            .then(response => response.json())
            .then(jsonD => setJsonData(jsonD), 
                setLoading(false),
                console.log(loading, '3'))
            .catch(error => {
                console.error(error);
            });
    }, [query])  

    return (
        <ScrollView >
            <Text>Bartender</Text>
            <SearchBar
                placeholder="Type Here..."
                onChangeText={setQuery}
                value={query}
            />
           <>
                { 
                    loading ? console.log('waiting for loading') : console.log(jsonData.drinks[0].idDrink)
                }
           </>    
        </ScrollView >
    );
}

I tried to fix the problem by setting loading variable, which would render the object after it is fully loaded, but it does not fix it.

If I run the app displaying console log instead of specific element:

loading ? console.log('waiting for loading') : console.log('the data should be loaded'),

this is the output:

[Thu Dec 17 2020 20:20:07.902]  LOG      change
[Thu Dec 17 2020 20:20:08.159]  LOG      true 1
[Thu Dec 17 2020 20:20:08.175]  LOG      waiting for loading
[Thu Dec 17 2020 20:20:13.218]  LOG      true 2
[Thu Dec 17 2020 20:20:13.284]  LOG      true 3
[Thu Dec 17 2020 20:20:13.451]  LOG      false 1
[Thu Dec 17 2020 20:20:13.486]  LOG      the data should be loaded
[Thu Dec 17 2020 20:20:14.761]  LOG      false 1
[Thu Dec 17 2020 20:20:14.775]  LOG      the data should be loaded

after I type a character in the search bar, the output is:

[Thu Dec 17 2020 20:23:54.892]  LOG      false 1
[Thu Dec 17 2020 20:23:54.938]  LOG      the data should be loaded
[Thu Dec 17 2020 20:23:55.526]  LOG      false 2
[Thu Dec 17 2020 20:23:55.781]  LOG      false 3
[Thu Dec 17 2020 20:23:55.807]  LOG      false 1
[Thu Dec 17 2020 20:23:55.840]  LOG      the data should be loaded
[Thu Dec 17 2020 20:23:56.294]  LOG      false 1
[Thu Dec 17 2020 20:23:56.312]  LOG      the data should be loaded

TLDR: How do I make sure that I access the object refreshed by hooks after it is fully loaded? app

1

There are 1 answers

3
bas On BEST ANSWER

I recommend you to go about the problem differently.

Instead of making the default value of jsonData an array you could set the default value to null:

function HomeScreen() {
  const [query, setQuery] = useState("");
  const [jsonData, setJsonData] = useState(null);

  useEffect(() => {
    fetch("https://www.thecocktaildb.com/api/json/v1/1/search.php?s=" + query, {
      method: "GET",
    })
      .then((response) => response.json())
      .then((jsonD) => setJsonData(jsonD))
      .catch((error) => {
        console.error(error);
      });
  }, [query]);

  return (
    // ...
       <>{jsonData && jsonData.drinks[0].idDrink}</>
    // ...
  );
}

This way you also don't need keep track of and update a loading state.

The problem with the other approach as you've experienced is that you can't be sure that the jsonData state has been updated before the loading state has.