while I am trying to develop my app, i keep getting the following error:
TypeError: Invalid attempt to spread non-iterable instance.
The error states a spread operator is being placed on a non-iterable but I am doing this on an array so it does not make sense to why I am receiving this error. I believe the error is occurring between these lines of code:
const Display = ({persons, setPersons, setFilterChecker, setErrorMessage, filter}) => {
const [counter, setCounter] = useState(0)
const [findNames, setFindNames] = useState([])
const [findNumbers, setFindNumbers] = useState([])
const copyOfNames = [...findNames]
const copyOfNumbers = [...findNumbers]
const copy = [...persons]
for (let j = 0; j < copy.length; j++) {
if ((copy[j].name).includes(filter)) {
setFindNames(copyOfNames.push(copy[j].name))
setFindNumbers(copyOfNumbers.push(copy[j].number))
}
}
However, here is the full code of Display.js which contains the above code:
import { useEffect, useState } from 'react'
import phoneService from '../services/information'
const handleDelete = (i, persons, setPersons, name2, setFilterChecker, setErrorMessage, setCounter, counter, findNames) => {
if (window.confirm(`delete ${name2} ?`)) {
const newArrayOfPeople = persons.filter(person => person.number !== findNames[i].number)
console.log(newArrayOfPeople)
const newArrayOfNames = newArrayOfPeople.map(person => person.name)
setFilterChecker(newArrayOfNames)
setPersons(newArrayOfPeople)
console.log(persons[i].id)
phoneService.remove(persons[i].id)
setErrorMessage(`You have successfully deleted ${name2} from the list.`)
setCounter(counter + 1)
}
}
const Display = ({persons, setPersons, setFilterChecker, setErrorMessage, filter}) => {
const [counter, setCounter] = useState(0)
const [findNames, setFindNames] = useState([])
const [findNumbers, setFindNumbers] = useState([])
const copyOfNames = [...findNames]
const copyOfNumbers = [...findNumbers]
const copy = [...persons]
for (let j = 0; j < copy.length; j++) {
if ((copy[j].name).includes(filter)) {
setFindNames(copyOfNames.push(copy[j].name))
setFindNumbers(copyOfNumbers.push(copy[j].number))
}
}
if (filter) {
return (
findNames.map((name, i) => <div id='parentContainer'><nobr key={name}>{name} {findNumbers[i]}</nobr> <button onClick={() => handleDelete(i, persons, setPersons, name, setFilterChecker, setErrorMessage, setCounter, counter, findNames)}>delete</button></div>)
)
} else {
return ''
}
}
export default Display
Why is this occurring if an array IS iterable?
I believe the error is occurring specifically with the variables copyOfNames and copyOfNumbers.
Array.push returns a new length of array (number), not array.
You should do something like
for (....) {
copyOfNames.push(copy[j].name)
copyOfNumbers.push(copy[j].number)
}
setFindNames(copyOfNames)
setFindNumbers(copyOfNumbers)
change
setFindNames(copyOfNames.push(copy[j].name))
setFindNumbers(copyOfNumbers.push(copy[j].number))
to
setFindNames(names => [...names, copy[j].name])
setFindNumbers(numbers => [...numbers, copy[j].number])
Related
I created a custom hook that takes a string and 3 arrays of Users, called useSearchUsers
import { useCallback } from 'react';
export default function useSearchUsers() {
const searchUsers = useCallback((searchValue, assistants, interested, rejected) => {
const assistantResults = [];
const interestedResults = [];
const rejectedResults = [];
if (searchValue !== '') {
if (assistants.length > 0) {
assistants.forEach(user => {
if (user.assistantName.includes(searchValue)) {
const name = user.assistantName;
const image = user.assistantImage;
const id = user.assistantId;
assistantResults.push({
id,
name,
image
});
}
});
}
if (interested.length > 0) {
interested.forEach(user => {
if (user.interestedName.includes(searchValue)) {
const name = user.interestedName;
const image = user.interestedImage;
const id = user.interestedId;
interestedResults.push({
id,
name,
image
});
}
});
}
if (rejected.length > 0) {
rejected.forEach(user => {
if (user.rejectedName.includes(searchValue)) {
const name = user.rejectedName;
const image = user.rejectedImage;
const id = user.rejectedId;
rejectedResults.push({
id,
name,
image
});
}
});
}
}
}, []);
return searchUsers;
}
And on the screen where I want to call that hook I have a TextInput where the user can write a string. Here I have declared the assistants, interested and rejected arrays, but for obvious reasons I'm ommiting them here
import useSearchUsers from '../../hooks/assistance/useSearchUsers';
const [eventAssistants, setEventAssistants] = useState([]);
const [eventInterested, setEventInterested] = useState([]);
const [eventRejected, setEventRejected] = useState([]);
const [searchText, setSearchText] = useState('');
const searchUsers = useSearchUsers();
export default function Screen(props) {
return (
<Container>
<SafeAreaProvider>
<TextInput onChangeText={text => setSearchText(text)} />
<Button onPress={() => useSearchUsers(searchText, eventAssistants, eventInterested, eventRejected)} />
</SafeAreaProvider>
</Container>
);
}
My question is, how can I return the 3 results arrays from the hook and passing them to the screen?
You can return an array containing the 3 result arrays.
return [assistantResults, interestedResults, rejectedResults];
instead of return searchUsers;
And to consume it in Screen you can destructure the hook's return value.
const [assistantResults, interestedResults, rejectedResults] = useSearchUsers();
Last edit of the night. Tried to clean some things up to make it easier to read. also to clarify what is going on around the useEffect. Because I am running react in strict mode everything gets rendered twice. The reference around the useEffect makes sure it only gets rendered 1 time.
Db is a firebase reference object. I am grabbing a list of league of legends games from my database.
one I have all my games in the snapshot variable, I loop through them to process each game.
each game contains a list of 10 players. using a puuId I can find a specific player. We then pull the data we care about in addChamp.
The data is then put into a local map. We continue to update our local map untill we are done looping through our database data.
After this I attempt to change our state variable in the fetchMatches function.
My issue now is that I am stuck in an infinite loop. I think this is because I am triggering another render after the state gets changed.
import { useState, useEffect, /*useCallback,*/ useRef } from 'react'
import Db from '../Firebase'
const TotGenStats = ({ player }) => {
const [champs, setChamps] = useState(new Map())
var init = new Map()
var total = 0
console.log("entered stats")
const addChamp = /*useCallback(*/ (item) => {
console.log("enter add champ")
var min = item.timePlayed/60
//var sec = item.timePlayed%60
var kda = (item.kills + item.assists)/item.deaths
var dub = 0
if(item.win){
dub = 1
}
var temp = {
name: item.championName,
avgCs: item.totalMinionsKilled,
csMin: item.totalMinionsKilled/min,
kds: kda,
kills: item.kills,
deaths: item.deaths,
assists: item.assists,
wins: dub,
totalG: 1
}
init.set(item.championName, temp)
//setChamps(new Map(champs.set(item.championName, temp)))
}//,[champs])
const pack = /*useCallback( /*async*/ (data) => {
console.log("enter pack")
for(const item of data.participants){
//console.log(champ.assists)
if(item.puuid === player.puuid){
console.log(item.summonerName)
if(init.has(item.championName)){//only checking init??
console.log("update champ")
}
else{
console.log("add champ")
/*await*/ addChamp(item)
}
}
}
}/*,[addChamp, champs, player.puuid])*/
const fetchMatches = async () => {
console.log("enter fetch matches")
Db.collection("summoner").doc(player.name).collection("matches").where("queueId", "==", 420)
.get()
.then((querySnapshot) => {
querySnapshot.forEach(async (doc) => {
//console.log("loop")
console.log(doc.id, " => ", doc.data());
console.log("total: ", ++total);
await pack(doc.data());
});
})
.then( () => {
setChamps(init)
})
.catch((error) => {
console.log("error getting doc", error);
});
}
const render1 = useRef(true)
useEffect( () => {
console.log("enter use effect")
if(render1.current){
render1.current = false
}
else{
fetchMatches();
}
})
return(
<div>
<ul>
{[...champs.keys()].map( k => (
<li key={k}>{champs.get(k).name}</li>
))}
</ul>
</div>
)
}
export default TotGenStats
Newest Version. no longer infinitly loops, but values do not display/render.
import { useState, useEffect } from 'react'
import Db from '../Firebase'
const TotGenStats = ({ player }) => {
const [champs, setChamps] = useState(new Map())
var total = 0
console.log("entered stats")
const addChamp = /*useCallback(*/ (item) => {
console.log("enter add champ")
var min = item.timePlayed/60
//var sec = item.timePlayed%60
var kda = (item.kills + item.assists)/item.deaths
var dub = 0
if(item.win){
dub = 1
}
var temp = {
name: item.championName,
avgCs: item.totalMinionsKilled,
csMin: item.totalMinionsKilled/min,
kds: kda,
kills: item.kills,
deaths: item.deaths,
assists: item.assists,
wins: dub,
totalG: 1
}
return temp
}
useEffect(() => {
var tempChamp = new Map()
Db.collection("summoner").doc(player.name).collection("matches").where("queueId","==",420)
.get()
.then((querySnapshot) => {
querySnapshot.forEach(async (doc) => {
console.log(doc.id," => ", doc.data());
console.log("total: ", ++total);
for(const person of doc.data().participants){
if(player.puuid === person.puuid){
console.log(person.summonerName);
if(tempChamp.has(person.championName)){
console.log("update ", person.championName);
//add update
}else{
console.log("add ", person.championName);
var data = await addChamp(person);
tempChamp.set(person.championName, data);
}
}
}
})//for each
setChamps(tempChamp)
})
},[player.name, total, player.puuid]);
return(
<div>
<ul>
{[...champs.keys()].map( k => (
<li key={k}>{champs.get(k).name}</li>
))}
</ul>
</div>
)
}
export default TotGenStats
useEffect will be called only once when you will not pass any argument to it and useEffect works as constructor hence its not possible to be called multiple times
useEffect( () => {
},[])
If you pass anything as argument it will be called whenever that argument change is triggered and only in that case useEffect will be called multiple times.
useEffect( () => {
},[arg])
Though whenever you update any state value in that case component will re-render. In order to handle that situation you can use useCallback or useMemo.
Also for map operation directly doing it on state variable is not good idea instead something like following[source]:
const [state, setState] = React.useState(new Map())
const add = (key, value) => {
setState(prev => new Map([...prev, [key, value]]))
}
I have made some edits to your latest code try following:
import { useState, useEffect, useRef } from "react";
import Db from "../Firebase";
const TotGenStats = ({ player }) => {
const [champs, setChamps] = useState(new Map());
const addChamp = (item) => {
let min = item.timePlayed / 60;
let kda = (item.kills + item.assists) / item.deaths;
let dub = null;
if (item.win) {
dub = 1;
} else {
dub = 0;
}
let temp = {
name: item.championName,
avgCs: item.totalMinionsKilled,
csMin: item.totalMinionsKilled / min,
kds: kda,
kills: item.kills,
deaths: item.deaths,
assists: item.assists,
wins: dub,
totalG: 1,
};
setChamps((prev) => new Map([...prev, [item.championName, temp]]));
};
const pack = (data) => {
for (const item of data.participants) {
if (item.puuid === player.puuid) {
if (!champs.has(item.championName)) {
addChamp(item);
}
}
}
};
const fetchMatches = async () => {
Db.collection("summoner")
.doc(player.name)
.collection("matches")
.where("queueId", "==", 420)
.get()
.then((querySnapshot) => {
querySnapshot.forEach(async (doc) => {
await pack(doc.data());
});
})
.catch((error) => {});
};
const render1 = useRef(true);
useEffect(() => {
fetchMatches();
});
return (
<div>
<ul>
{[...champs.keys()].map((k) => (
<li key={k}>{champs.get(k).name}</li>
))}
</ul>
</div>
);
};
export default TotGenStats;
I am trying to get values from a list using index value via for loop. but after getting and assigning them to another list it shows all values are undefined. How to solve this
import React, { useEffect, useContext, useState } from 'react';
import { useHistory } from 'react-router-dom';
function Function() {
const history = useHistory();
const [data, setData] = useState({});
const [features, setFeatures] = useState({});
useEffect(() => {
const passingData = history.location.state; //array passed from prev page
setData(passingData);
console.log(data);//log=> {'id':01 ,'name':"name1"},{'id':02 ,'name':"name2"},{'id':03 ,'name':"name3"}
const tFeatures = [];
for (var i = 0; i < 2; i++) {
tFeatures.push(data[i]);
}
console.log(tFeatures);//output => [undefined, undefined, undefined]
}, []);
return <div></div>;
}
export default Function;
console.log(data); // {'id':01 ,'name':"name1"},{'id':02 ,'name':"name2"},{'id':03 ,'name':"name3"}
console.log(tFeatures) // [undefined, undefined, undefined]
Why don't you use passingData instead of data? By the time you call console.log(data), it won't have been set yet (it's asynchronous). So either use passingData or extract the part that depends on data and put it in its own useEffect().
function Function() {
const history = useHistory();
const [data, setData] = useState([]); // should be an array, not an object
const [features, setFeatures] = useState([]); // should be an array, not an object
useEffect(() => {
const tFeatures = [];
for (var i = 0; i < 2; i++) {
tFeatures.push(data[i]);
}
}, [data])
useEffect(() => {
const passingData = history.location.state;
setData(passingData);
}, [history.location.state]);
return <div></div>;
}
or
function Function() {
const history = useHistory();
const [data, setData] = useState([]); // should be an array, not an object
const [features, setFeatures] = useState([]); // should be an array, not an object
useEffect(() => {
const passingData = history.location.state;
setData(passingData);
const tFeatures = [];
for (var i = 0; i < 2; i++) {
tFeatures.push(passingData[i]); // use passingData not data
}
}, [history.location.state]);
return <div></div>;
}
How could I have n states in a React component
Assuming that the component won't receive this n value in any props, is something that it will get from a database
Using useState will create the state, setState for each pair, but I need n pairs
Rafael
JavaScript arrays doesn't have a fixed length.
You can do something like
const [arr, setArr] = useState([]);
And when you receive n values from database just set it to the array using setArr(values)
Now arr will be an array containing n elements retrieved from database. You can then iterate over it and render them as you wish.
As T J pointed out. You can use an array in state.
Or, another option is to map n Components for each item, therefore instantiating n states.
const Example = (props) => {
const [data, setData] = useState();
useEffect(() => {
// ...fetch data
// setData(data);
});
if (data === undefined) {
return null;
}
return data.map((data) => <Item data={data} />);
};
const Item = (props) => {
const [state, setState] = useState(props.data);
return <>Example</>;
};
Or if n is literally just a number, a count. Then you could do something like this.
const Example = (props) => {
const [count, setCount] = useState();
useEffect(() => {
// ...fetch count
// setCount(count);
});
if (count === undefined) {
return null;
}
const items = [];
for (var i = 1; i <= count; i++) {
items.push(<Item />);
}
return items;
};
const Item = (props) => {
const [state, setState] = useState();
return <>Example</>;
};
Below is my code with a search input hoos and I can't identify why it isn't working.
import Herois from './json/videos.json'
function App() {
const [valueInput, setValueInput] = useState('')
const [newArray, setNewArray] = useState([])
useEffect(() => {
const results = Herois.filter((i) => {
i.title.toLowerCase().includes(valueInput.toLowerCase())
})
setNewArray(results)
console.log(newArray)
}, [valueInput])
}
is always becoming an empty array
const results = Herois.filter((i) => {
// you have to return the something here
return i.title.toLowerCase().includes(valueInput.toLowerCase())
})
or
const results = Herois.filter((i) => (i.title.toLowerCase().includes(valueInput.toLowerCase())
))