javascript – Passing functions into other components in React

I’m working on cleaning up the code in my first project by breaking some of the components down into smaller pieces, but I am struggling to find the best practice on passing a function from one component to another. There is also the possibility I am just going about this all wrong! Any advice is appreciated. Below is the code for a function I’d like to be able to use in multiple components ScryfallQueryin this specific instance I am trying to pass it into the FileHandler component which is currently rendering the following error:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'ScryfallQuery')
    at fileHandler.js:50:1
    at Array.forEach (<anonymous>)
    at updateCards (fileHandler.js:43:1)
    at fileHandler.js:67:1
    at invokePassiveEffectCreate (react-dom.development.js:23487:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994:1)
    at invokeGuardedCallback (react-dom.development.js:4056:1)
    at flushPassiveEffectsImpl (react-dom.development.js:23574:1)
    at unstable_runWithPriority (scheduler.development.js:468:1)

If anyone could point me to the correct literature that would also be helpful. The react documentation here https://reactjs.org/docs/faq-functions.html doesn’t seem to have correct way to pass functions with the syntax I have at hand (maybe thats the problem?). I’m still learning React (and js!) so there maybe something obvious here I am missing.

But to reiterate, I’m looking for the best way to pass a function, in this case, my API query into multiple components.

Here is my App.js code:

    import {useState, useEffect} from 'react'
    import './styles/App.css';
    import FileHandler from './Components/fileHandler';
    import ImagePreviewer from './Components/ImagePreviewer';
    import { Header } from './Components/Header';
    import { Comparison } from './Components/Comparison';
    import {ScryfallQuery} from './Components/ScryfallQuery';
    
function App() {
          const [cardInput, setCardInput] = useState([]);
          const [cards, setCards] = useState([]);
          const [previewCard, setPreviewCard] = useState([]);
          const [comparisonCard, setComparisonCard] = useState([]);
        
          return (
            <div className="container"> 
              {/* <GlobalState> */}
                <Header />
                {cards && <Comparison comparison={comparisonCard} setComparisonCard={setComparisonCard}/>}
                <div className="row" id="table-main">
                  <div className="col-3">
                    <ImagePreviewer previewCard={previewCard} />
                  </div>
                  <div className="col-9">
                    <FileHandler 
                      cardInput={cardInput}
                      setCardInput={setCardInput}
                      
                      previewCard={previewCard} 
                      setPreviewCard={setPreviewCard}
        
                      cards={cards}
                      setCards={setCards}
        
                      scryfallQuery={ScryfallQuery}
                      
                    />
                  </div>
                </div>
              {/*</GlobalState> */}
              </div>    
          );
        }
        
        export default App;

Here is the code for the component holding the function:

export const ScryfallQuery = () => {
     
    /*collection query function for more info read 
    here: https://scryfall.com/docs/api/cards */ 
    
    const queryString = `https://api.scryfall.com/cards/collection`;
    const queryStringified = JSON.stringify(query);
    
    return fetch(queryString, {
        method: 'POST',
        headers: {
        'Content-Type': 'application/json'
        },
        body: queryStringified
    }).then((res, err) => {
        if (res) return res.json();
        if (err) return new Error("Error: ", err);
    });
    
    /* scryfall api accepts maximum 75 card query, 
    batches query in 75 increments */

    function sliceIntoBatches(arr, batchSize) {;
        const batchArr = []
        for (let i = 0; i < arr.length; i += batchSize) {
            const batch = arr.slice(i, i + batchSize);
            batchArr.push(batch);
        }
        return batchArr;
    };
}

and lastly here is the code for the file I am passing the function into:

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

export const FileHandler = ({
  cardInput, setCardInput, 
  cards, setCards, 
  previewCard, setPreviewCard,
  ScryfallQuery
}) => {

  const reader = new FileReader();
  reader.addEventListener('load', function(){
      const allCards = this.result.split(/r?n/g);
      setCardInput(allCards)   
  });


/////////////////////////////////////////////////////////////////
    /* scryfall api accepts maximum 75 card query, 
    batches query in 75 increments */
    function sliceIntoBatches(arr, batchSize) {;
        const batchArr = []
        for (let i = 0; i < arr.length; i += batchSize) {
            const batch = arr.slice(i, i + batchSize);
            batchArr.push(batch);
        }
        return batchArr;
    }

/////////////////////////////////////////////////////////////////

    const parseInput = (e) => {
      e.preventDefault();
      setCardInput([]);
      reader.readAsText(document.getElementById('input').files[0])
    }

    const updateCards = () => {
      let response;

      const cardBatches = sliceIntoBatches(cardInput, 75);

      cardBatches.forEach(async (cardBatch) => {
        const queryArray = cardBatch.filter(cardName => cardName !== '').map(cardName => {
          return {
            name: cardName
          }
        }); 

        response = await this.ScryfallQuery({ identifiers: queryArray });

        if (response) {
          console.log("response", response);
          if (response.data.length > 0) {
            console.log("second check", cards)
            const newCards = [...cards, ...response.data];
            setCards(newCards);
          } else if (response.not_found.length > 0 || response.data.length <= 0) {
            console.error("Cards not found: ", response.not_found);
          }}
      });
    }

    useEffect(() => {
      console.log("loading");
      if (cardInput.length > 0 && cards.length === 0) {
        updateCards();
      }
    }, [cardInput]);


/////////////////////////////////////////////////////////////////

    function scryfallCollectionQuery(query) {
        const queryString = `https://api.scryfall.com/cards/collection`;
        const queryStringified = JSON.stringify(query);
      
        return fetch(queryString, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: queryStringified
        }).then((res, err) => {
          if (res) return res.json();
          if (err) return new Error("Error: ", err);
        });
      }

/////////////////////////////////////////////////////////////////      
    return(
         <div className="App">
            <h1>Submit a .txt file of cards! </h1>
            <form target="_self" onSubmit={parseInput}>
                <input type="file" id="input" />
                <button id="submit">Submit</button>
                <pre id="preReader"></pre>
            </form>
            
           {cards &&
              <table id="table-head">
                <tbody>  
                  <tr>
                    <td id="card-number-title">#</td>
                    <td id="card-name-title">Card Name</td>
                    <td id="card-data-title">Colors</td>
                    <td id="card-data-title">Rarity</td>
                    <td id="card-data-title">ELO</td>
                  </tr>
                </tbody>
              </table> } 
          {cards && cards.map(card => 
            <Card
              card={card} 
              setPreviewCard={setPreviewCard} />
            )} 
        </div>   
    )
}

export default FileHandler;

Leave a Comment