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 ScryfallQuery
, in 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;
CodePudding user response:
You pass the reference to the function as scryfallQuery
prop (with lowercase s
).
<FileHandler
cardInput={cardInput}
setCardInput={setCardInput}
previewCard={previewCard}
setPreviewCard={setPreviewCard}
cards={cards}
setCards={setCards}
scryfallQuery={ScryfallQuery}
/>
Then inside FileHandler
you try to use it with uppercase S
:
export const FileHandler = ({
cardInput, setCardInput,
cards, setCards,
previewCard, setPreviewCard,
ScryfallQuery
})
And why exactly are you using this
here:
response = await this.ScryfallQuery({ identifiers: queryArray });
I don't know if those are all the mistakes but your code is a mess and it's hard to read. Would be nice to get only the relevant code snippets.
CodePudding user response:
Did you forget to import ScryfallQuery
into App.js?
CodePudding user response:
if u want to pass a function from a parent to children u have to use a prop
example :
const parent = () =>{
const handleconsolelog = () =>{
console.log("hi")
}
return (
<children handleconsolelog={handleconsolelog}
)
}
then in the children :
const childeren= ({handleconsolelog}) =>{
return (
<button onClick={handleconsolelog} />
)
}