event handler in functional Component with hooks in React

Viewed 125

I'm trying to understand functional components and hooks. There is a component which download array of Quotes from API and should randomly pick one and send it as a prop to child component "Quote".

import React, {useState, useEffect} from 'react';
import Quote from './Quote.js';

function App() {
 const[quotes, setQuotes]=useState();
 const[randomQuote, setRandomQuote]=useState();

 useEffect(()=>{
   fetch("https://type.fit/api/quotes").then(
     response=>response.json()).then(
     data=>{setQuotes(data);
     newRandomQuote()}
   )},
 [])

 const newRandomQuote=()=>{
 var randomQuote=quotes[Math.floor(Math.random() * 1643)];  // it's array of 1643 quotes
 setRandomQuote(randomQuote);
 }

 return (
   <div>
    {quotes!=null&&<Quote quote={randomQuote}/>}
   </div>
 );
}

export default App;

But it seems like my newRandomQuote function didn't work, my randomQuote state is undefined, i don't understand it because it seems like i can access quotes State from outside of newRandomQuote but it is undefined from inside. If i add console.log(quotes) somewhere in component like this:

 const newRandomQuote=()=>{
  var randomQuote=quotes[Math.floor(Math.random() * 1643)];
  setRandomQuote(randomQuote);
  }

  console.log(quotes);

  return (
    <div>
     {quotes!=null&&<Quote quote={randomQuote}/>}
    </div>
  );

It works and show me array, so i don't get it.

3 Answers

Declare newRandomQuote method above useEffect, I think that should work.

Create a useEffect that reacts with quotes state.

The reason it was undefined in your code was because the newRandomQuote called when quotes was still undefined. It's value will be updated only in next update or function call. This is why you need to create a new useEffect that tracks quotes and set the randomQuote.

import React, {useState, useEffect} from 'react';
import Quote from './Quote.js';

function App() {
 const[quotes, setQuotes]=useState();
 const[randomQuote, setRandomQuote]=useState();

 useEffect(()=>{
   fetch("https://type.fit/api/quotes")
     .then(response => response.json())
     .then(
       data => {
        setQuotes(data);
       }
     )},
 [])

 useEffect(() => {
   if (quotes) {
     const randomQuote = quotes[Math.floor(Math.random() * 1643)];  // it's array of 1643 quotes
     setRandomQuote(randomQuote);
   }
 }, [quotes])


 return (
   <div>
    {
      // Check against randomQuote instead of quotes
      // because, in the 2nd call, randomQuote will be undefined
      // while quotes will have response data
      // and also that's the value you need here
      randomQuote !== null && 
        <Quote quote={randomQuote}/>
    }
   </div>
 );
}

export default App;

The const/let variables are not hoisted so you cannot use it before it's declaration.

Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution. Inevitably, this means that no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local.

You could use normal function instead if function expression. The function can be used before it's definition. function are hoisted in JavaScript.

And on the component mount and on first execution of the useEffect the newRandomQuote function will have initial state, because the state update is async.

You need to call the newRandomQuote() in componentDidUpdate lifecycle method. Which is equal to useEffect with dependency array.

Always initialized the state with the expected data type.

Try this.

import React, { useState, useEffect } from 'react';
import Quote from './Quote.js';

function App() {
    const [quotes, setQuotes] = useState([]);
    const [randomQuote, setRandomQuote] = useState([]);

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

    useEffect(() => {
        newRandomQuote();
    }, [quotes]);

    async function fetchData() {
        try{
            let response = await fetch("https://type.fit/api/quotes");
            let data = await response.json();
            setQuotes(data);
        } catch(error) {
            console.log(error);
        }

    }
    function newRandomQuote() {
        if (quotes.length > 0) {
            let randomQuote = quotes[Math.floor(Math.random() * 1643)];
            setRandomQuote(randomQuote);
        }
    }

    return (
        <div>
            {quotes != null && <Quote quote={randomQuote} />}
        </div>
    );
}
export default App;

You could also use async/await for better readability.

Related