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>;
}
Related
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])
I'm using React's context api to store an array of Favorite products.The favorites Array is filled with Boolean Value False and turned to true based on id of the products.There is collection page which displays productCards having an addtoFavorite button,Upon clicking the button disables but if the product is already present in favorites it has to disabled.
Now it works perfectly fine for the 1st Page , disabling only favorite products with the array containing values true and false based on index of the products but when navigated to another page it disables other products at the same index even though the favorites array is updated to have all values as false.If we Navigate Back or move to another page its value now remains false in the array.It looks as if UseContext updates the value of the array late or doesn't rerender on change.
I have tried implementing other stuffs but it still wouldn't re-render when the array was changed.
Here's the FavoritesContext:
const FavoritesContext = React.createContext({
addToFavorites: (id,index) => {},
favorites:[],
storedFavorites:(data) => {}
});
export const FavoritesContextProvider = (props) => {
const authCtx = useContext(AuthContext)
const token = authCtx.token;
const userId = authCtx.userId;
const [favorites,setFavorites] = useState([]);
// To retrieve stored favorites from FireBase
const retrieveStoredFavorites = (data) => {
let fav = new Array(data.length).fill(false);
setFavorites(fav);
let queryParams = '?auth=' + token + '&orderBy="userId"&equalTo="' + userId + '"';
axiosInstance.get('/Favorites.json' + queryParams)
.then((response) => {
let fetchProductData = [];
for (let key in response.data) {
fetchProductData.push({
...response.data[key],
productId: key,
});
}
let favoriteList = [];
//To find if the product is present in the Fetched Favorite products List
for(let i=0;i<data.length;i++){
let ids = data[i].id
let favoriteProducts = !!fetchProductData.find((product)=>product.id==ids)
favoriteList.push(favoriteProducts)
}
//console.log(favoriteList)
setFavorites(favoriteList)
});
}
//Add to Favorites
const addTofavoritesHandler = (Product,index) => {
axiosInstance
.post('Favorites.json?auth='+token,Product)
.then((response) => {
//console.log("SUCCESS")
})
.catch((error) => console.log(error));
let favoriteOnes = [...favorites];
favoriteOnes[index] = true;
setFavorites(favoriteOnes);
};
const contextValue = {
addToFavorites:addTofavoritesHandler,
favorites:favorites,
storedFavorites:retrieveStoredFavorites
};
return (
<FavoritesContext.Provider value={contextValue}>
{props.children}
</FavoritesContext.Provider>
);
};
export default FavoritesContext;
Now here is the Collection Page
const CollectionPage = () => {
const classes = useStyles();
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [productsPerPage] = useState(9);
const [loading, setLoading] = useState(false);
const { enqueueSnackbar } = useSnackbar();
const authCtx = useContext(AuthContext);
const token = authCtx.token;
const userId = authCtx.userId;
const favoriteCtx = useContext(FavoritesContext)
const favorites = favoriteCtx.favorites
//To Display the Products in Main Content
const DisplayProductsHandler = (Data) => {
//Get value of FirstPageNumber and LastPageNumber
const indexOfLastPage = currentPage * productsPerPage;
const indexOfFirstPage = indexOfLastPage - productsPerPage;
//console.log("[Products]")
const productData = Data.slice(indexOfFirstPage, indexOfLastPage);
favoriteCtx.storedFavorites(productData)
//console.log(productData);
const updatedProductData = productData.map((product,index) => {
return (
<ProductCard
Link={`/Info/${product.id}`}
key={product.id}
Title={product.productName}
Image={product.productImage}
Value={product.price}
addToFavorites={() => addTofavoritesHandler(product,index)}
addToCart={() => addToCartHandler(product)}
disableFavoriteButton={favorites[index]}
/>
);
});
setProducts(updatedProductData);
};
//Display the Products from DisplayProductHandler
useEffect(() => {
setLoading(true);
//Scroll To Top When Reloaded
window.scrollTo(0, 0);
//To Display the Products
if (filteredProducts.length === 0) {
DisplayProductsHandler(ProductData);
} else {
DisplayProductsHandler(filteredProducts);
}
setLoading(false);
}, [currentPage, filteredProducts]);
//Add to Favorites Handler
const addTofavoritesHandler =(likedProduct,index) => {
setLoading(true);
let updatedLikedProduct = {
...likedProduct,
userId: userId,
};
favoriteCtx.addToFavorites(updatedLikedProduct,index)
//To Display ADDED TO FAVORITES Message using useSnackbar()
enqueueSnackbar("ADDED TO FAVORITES", { variant: "success" })
setLoading(false);
};
I need it to re-render every time the array in context is updated.
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())
))
let { photos, isQuering, empty, error } = useFetch(brand, isOld);
useEffect(() => {
if (isOld) {
const { photos: photosTest } = useFetch(brand, isOld);
photos = photosTest;
}
}, [isOld]);
useFetch is a custom hook that I have and I want to bring the old photos when the isOld state is true, the code above useEffect is called normally and the photos load, but I run into the error that useFetch is not being called inside the body a function component, the following error appears "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:", that is, I am doing something very wrong that I cannot to see! If you can help me, I would appreciate it very much!
Editing because of Danko! The Hook!
import { useEffect, useState, useContext } from 'react';
import { useScrollPagination } from './flow-manager';
import { db } from '../../Firebase';
import { userContext } from '../appContext';
export default function fetch(brand, isOld) {
const {
userData: { uid },
} = useContext(userContext);
const [photos, setPhotos] = useState([]);
const [lastDoc, setLastDoc] = useState(undefined);
const [isQuering, setIsQuering] = useState(false);
const [empty, setEmpty] = useState(false);
const [error, setError] = useState();
const [finished, setFinished] = useState(false);
const shouldFetchMore = useScrollPagination();
const [shouldKeepFecthing, setShouldKeepFetching] = useState(false);
useEffect(() => {
if (isQuering || finished) return;
if (!lastDoc || shouldFetchMore || shouldKeepFecthing) {
setIsQuering(true);
let query = !isOld
? db
.collection('catalog-images')
.where('brandName', '==', brand)
.orderBy('timestamp', 'desc')
.endBefore(new Date().setDate(new Date().getDate() - 40))
.limit(20)
: db
.collection('catalog-images')
.where('brandName', '==', brand)
.where('photoPeriod', '==', 'Antiga')
.limit(20);
if (lastDoc) query = query.startAfter(lastDoc);
query
.get()
.then(snap => {
const newPhotos = [];
let valid = 0;
snap.forEach(doc => {
const { url, pricetag, timestamp } = doc.data();
if (!uid && pricetag === 'Sim') return;
brand && newPhotos.push({ url, timestamp });
valid += 1;
});
setPhotos(oldPhotos => [...oldPhotos, ...newPhotos]);
setShouldKeepFetching(valid < 10);
setEmpty(snap.empty);
setLastDoc(snap.docs[snap.docs.length - 1]);
setFinished(snap.docs.length < 20);
setIsQuering(false);
})
.catch(setError);
}
}, [!!lastDoc, shouldFetchMore, shouldKeepFecthing, isQuering]);
return { photos, isQuering, empty, error, fetch };
}
Last Update:
Here, where I am calling the hook:
let {
photos,
isQuering,
empty,
error,
useFetch: refetch,
} = useFetch(brand, isOld);
useEffect(() => {
if (isOld) {
let { photos: photosTest } = refetch(brand, isOld);
photos = photosTest;
setIsOld(false);
}
}, [isOld]);
Aaaand, the hook:
import { useEffect, useState, useContext } from 'react';
import { useScrollPagination } from './flow-manager';
import { db } from '../../Firebase';
import { userContext } from '../appContext';
export default function useFetch(brand, isOld) {
const {
userData: { uid },
} = useContext(userContext);
const [photos, setPhotos] = useState([]);
const [lastDoc, setLastDoc] = useState(undefined);
const [isQuering, setIsQuering] = useState(false);
const [empty, setEmpty] = useState(false);
const [error, setError] = useState();
const [finished, setFinished] = useState(false);
const shouldFetchMore = useScrollPagination();
const [shouldKeepFecthing, setShouldKeepFetching] = useState(false);
useEffect(() => {
if (isQuering || finished) return;
if (!lastDoc || shouldFetchMore || shouldKeepFecthing) {
setIsQuering(true);
let query = !isOld
? db
.collection('catalog-images')
.where('brandName', '==', brand)
.orderBy('timestamp', 'desc')
.endBefore(new Date().setDate(new Date().getDate() - 40))
.limit(20)
: db
.collection('catalog-images')
.where('brandName', '==', brand)
.where('photoPeriod', '==', 'Antiga')
.limit(20);
if (lastDoc) query = query.startAfter(lastDoc);
query
.get()
.then(snap => {
const newPhotos = [];
let valid = 0;
snap.forEach(doc => {
const { url, pricetag, timestamp } = doc.data();
if (!uid && pricetag === 'Sim') return;
brand && newPhotos.push({ url, timestamp });
valid += 1;
});
setPhotos(oldPhotos => [...oldPhotos, ...newPhotos]);
setShouldKeepFetching(valid < 10);
setEmpty(snap.empty);
setLastDoc(snap.docs[snap.docs.length - 1]);
setFinished(snap.docs.length < 20);
setIsQuering(false);
})
.catch(setError);
}
}, [!!lastDoc, shouldFetchMore, shouldKeepFecthing, isQuering]);
return { photos, isQuering, empty, error, useFetch };
}
I'd suggest something else:
update your useFetch so it will have refetch function end add it to returned object.
now, your updated hook can be destructured like this: const { photos, isQuering, empty, error, refetch } = useFetch(brand);
your useEfect can be used like this:
useEffect(() => {
if(isOld) {
refetch();
setIsOld(false)
}
}, [isOld]);
Update:
You must rename your custon hook to start with use. Otherwise there is no way for react to differ it from other functions. So, instead of naming it fetch rename it to useFetch.
The thing is, you can't call a hook from another hooks. Hooks are only called from component body (top-level). Your code makes no sense on a few levels:
let { photos, isQuering, empty, error } = useFetch(brand, isOld);
useEffect(() => {
if (isOld) {
const { photos: photosTest } = useFetch(brand, isOld); // can't call a hook here
photos = photosTest; // can't mutate component-level variables
}
}, [isOld]);