React useEffect does not fetch paramter into React useState - reactjs

Why does my article state doesnt have the same Parameter like my cart.filter element.
What am I doing wrong, using the useState Hook.
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
setArticle(cart.filter((e) => e.id === id));
fetchCartAndPrice();
}, []);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
}

In the moment that you are trying set the articles don't have carts yet. You need wait the cart update creating an exclusive useEffect to cart. Something like this:
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
fetchCartAndPrice();
}, []);
useEffect(() => {
setArticle(cart.filter((e) => e.id === id));
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);

When you trigger your function setArticle() the async function which fetch the cart didn't finished yet ... So it can't "filter" the (still empty) cart ...
You need to execute that filter thing after the cart is set :
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
}, []);
useEffect(() => {
// --> ensure we are not in the "init step"
if (cart.length) {
setArticle(cart.filter((e) => e.id === id));
// Not sur where this one belongs ... :
fetchCartAndPrice();
}
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
Another maner to do so is to set the article at the same place of the cart :
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
setArticle(sess.cart.filter((e) => e.id === id));
};
}, []);

Gabriel Furlan gave a great solution.
I would use the async declaration at the top level of the useEffect hook.
Ex.
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(async () => {
const sess = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
}, []);
useEffect(() => {
setArticle(cart.filter((e) => e.id === id));
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);

Related

Uncaught TypeError: Cannot read properties of undefined (reading 'filter')

I am trying to connect frontend and backend. I can't understand why filter() function is undefined. In localhost it worked but when i am trying to put it on web with fly.io it doesn't work anymore. Here's code
import { useState, useEffect } from 'react'
import Person from './components/Person'
import Filter from './components/Filter'
import NewPersonForm from './components/NewPersonForm'
import personService from './services/persons'
import Notification from './components/Notification'
import './index.css'
const App = () => {
const [persons, setPersons] = useState([])
const [newName, setNewName] = useState('')
const [newNumber, setNewNumber] = useState('')
const [newFilter, setNewFilter] = useState('')
const [successMessage, setSuccessMessage] = useState(null)
useEffect(() => {
console.log('effect')
personService
.getAll()
.then(response => {
console.log('fulfilled')
setPersons(response.data)
})
}, [])
const addPerson = (event) => {
event.preventDefault()
const personObject = {
name: newName,
number: newNumber
}
console.log(newName)
const inArray = persons.filter((person) => person.name === newName)
if (inArray.length === 0){
personService
.create(personObject)
.then(response => {
setPersons(persons.concat(response.data))
setSuccessMessage(
`Added ${personObject.name}`
)
setTimeout(() => {
setSuccessMessage(null)
}, 3000)
setNewName('')
setNewNumber('')
})
console.log('button clicked', event.target)
} else{
alert(`${newName} is already in the phonebook`)
}
}
const deletePerson = (person) => {
const persontodelete = person.name
if (window.confirm(`Delete ${persontodelete}?`)){
personService
.remove(person.id)
.then(response => {
setPersons(persons.filter(p => p.id !== person.id))
setSuccessMessage(
`Deleted ${person.name}`
)
setTimeout(() => {
setSuccessMessage(null)
}, 3000)
})
}
}
const filteredPersons = persons.filter(person =>
person.name.toLowerCase().includes(newFilter.toLowerCase()))
const handlePersonChange = (event) => {
console.log(event.target.value)
setNewName(event.target.value)
}
const handleNumberChange = (event) => {
console.log(event.target.value)
setNewNumber(event.target.value)
}
const handleFilterChange = (event) => {
setNewFilter(event.target.value)
}
return (
<div>
<h2>Phonebook</h2>
<Notification message={successMessage} />
<Filter newFilter={newFilter} handleFilterChange={handleFilterChange}/>
<h2>add new</h2>
<NewPersonForm
addPerson={addPerson}
newName={newName}
handlePersonChange={handlePersonChange}
newNumber={newNumber}
handleNumberChange={handleNumberChange}
/>
<h2>Numbers</h2>
<Person filteredPersons={filteredPersons} deletePerson={deletePerson}/>
<div>debug: {newName}</div>
<div>debug: {newNumber}</div>
</div>
)
}
export default App
I am trying to get array with names and numbers from backend. Error disappears if I do following change
useEffect(() => {
console.log('effect')
personService
.getAll()
.then(data => {
console.log('fulfilled')
setPersons(data)
})
}, [])
but new error occures in toLowecase() function.
const filteredPersons = persons.filter(person =>
person.name.toLowerCase().includes(newFilter.toLowerCase()))

How to effectively refresh the data in a custom hook using react-infinite-scroll-component when an item is deleted?

I am using a custom hook useInfiniteFetchSearch to fetch and search data for a infinite scroll component built using react-infinite-scroll-component.
The hook makes an API call and sets the data in the state using setData. Currently, I am using refreshData() method to refresh the data again when an item is deleted from the list.
However, I am not satisfied with this solution as it calls the API again even though I already have the data. Is there a more efficient way to refresh the data and update the infinite scroll component without making another API call?
Here is my custom hook implementation:
import { useState, useEffect, useRef } from "react";
import axios from "axios";
const useInfiniteFetchSearch = (api, resultsPerPage, sort = null) => {
const [data, setData] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(2);
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const searchTermRef = useRef(null);
useEffect(() => {
const searchData = async () => {
try {
setLoading(true);
let query = `${api}${
searchTerm === "" ? `?` : `?search=${searchTerm}&`
}page=1`;
query = sort ? `${query}&sort=${sort}` : query;
const result = await axios.post(query);
const fetchedData = result.data;
setData(fetchedData);
setPage(2);
setHasMore(fetchedData.length === resultsPerPage);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
searchData();
}, [searchTerm, api, resultsPerPage, sort]);
const refreshData = async () => {
try {
setLoading(true);
let query = `${api}${
searchTerm === "" ? `?` : `?search=${searchTerm}&`
}page=1`;
query = sort ? `${query}&sort=${sort}` : query;
const result = await axios.post(query);
const fetchedData = result.data;
setData(fetchedData);
setPage(2);
setHasMore(fetchedData.length === resultsPerPage);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const fetchMore = async () => {
try {
setLoading(true);
let query = `${api}?search=${searchTerm}&page=${page}`;
query = sort ? `${query}&sort=${sort}` : query;
const result = await axios.post(query);
const newData = result.data;
setData((prev) => [...prev, ...newData]);
setPage(page + 1);
setHasMore(newData.length === resultsPerPage);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const handleSearch = async (e) => {
e.preventDefault();
setSearchTerm(searchTermRef.current.value);
};
const handleDelete = async (e, itemId) => {
try {
await axios.delete(`${api}${itemId}`);
setData((prevData) => prevData.filter((item) => item.id !== itemId));
refreshData();
} catch (error) {
console.log(error);
} finally {
}
};
return {
state: { data, hasMore, loading, searchTermRef, searchTerm },
handlers: {
fetchMore,
setSearchTerm,
handleSearch,
handleDelete,
},
};
};
export default useInfiniteFetchSearch;
I am using this hook in my component:
const { state, handlers } = useInfiniteFetchSearch("/api/guides/search", 5);
const { data, hasMore, loading, searchTermRef, searchTerm } = state;
const { fetchMore, handleSearch, setSearchTerm, handleDelete } = handlers;
....
<InfiniteScroll
dataLength={data.length}
next={fetchMore}
hasMore={hasMore}
scrollableTarget="scrollableDiv"
loader={
<div className="flex justify-center items-center mx-auto">
<Loader />
</div>
}
>
<div className="space-y-1">
{data &&
data.map((item, index) => (
<GuidesItem
key={index}
guide={item}
handleDelete={handleDelete}
/>
))}
</div>
</InfiniteScroll>
I would appreciate any suggestions or solutions to this problem, thank you!

TypeError: Cannot read property 'map' of undefined React Hooks

I need some help understanding why I'm getting the error from the title: 'TypeError: Cannot read property 'map' of undefined'. I need to render on the page (e.g state & country here) some data from the API, but for some reason is not working.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return JSON.stringify(data);
}
useEffect(() => {
fetchData().then((res) => {
setUser(res)
setInfo(res.results);
})
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Can you guys provide me some help? Thanks.
Try this approach,
const APIFetch = () => {
const [user, setUser] = useState("");
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get("https://randomuser.me/api");
return data; <--- Heres is the first mistake
};
useEffect(() => {
fetchData().then((res) => {
setUser(res);
setInfo(res.data.results);
});
}, []);
const getName = (user) => {
const { state, country } = user.location; <--- Access location from the user
return `${state} ${country}`;
};
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>;
})}
</div>
);
};
Return data without stringify inside the fetchData.
Access user.location inside getName.
Code base - https://codesandbox.io/s/sharp-hawking-6v858?file=/src/App.js
You do not need to JSON.stringify(data);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return data.data
}
Do it like that
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('https://randomuser.me/api');
setUser(res.data);
setInfo(res.data.results);
}
featchData();
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Codesandbox: https://codesandbox.io/s/vigorous-lake-w52vj?file=/src/App.js

set Function not working in custom hook with useEffect

I'm working on a custom hook that relies on an async operation in useEffect. I cannot get my set function to actually set the value of the result of the async operation. In this case, country is always null in my App component so nothing is ever rendered. foundCountry gets set correctly, but setCountry doesn't seem to work. Thanks for the help!
const useCountry = name => {
const [country, setCountry] = useState([null]);
useEffect(() => {
const findCountry = async () => {
const foundCountry = await axios.get(
`https://restcountries.eu/rest/v2/name/${name}?fullText=true`
);
setCountry(foundCountry);
};
if (name !== '') findCountry();
}, [name]);
};
And here is my App component where I am using the custom hook
const App = () => {
const nameInput = useField('text');
const [name, setName] = useState('');
const country = useCountry(name);
const fetch = e => {
e.preventDefault();
setName(nameInput.value);
};
return (
<div>
<form onSubmit={fetch}>
<input {...nameInput} />
<button>find</button>
</form>
<Country country={country} />
</div>
);
};
You defined the custom hook, but you forgot to return the country state as the result:
const useCountry = name => {
const [country, setCountry] = useState([null]);
useEffect(() => {
const findCountry = async () => {
const foundCountry = await axios.get(
`https://restcountries.eu/rest/v2/name/${name}?fullText=true`
);
setCountry(foundCountry);
};
if (name !== '') findCountry();
}, [name]);
// you forgot to return it
return country;
};
You can try this
const useCountry = name => {
const foundCountry = await axios.get(
`https://restcountries.eu/rest/v2/name/${name}?fullText=true`
);
if (name !== '') return findCountry();
return;
};
//App container
const [country, setCountry] = useState('');
useEffect(() => {
setCountry(useCountry(name))
}, [name])

useEffect and async()

In the useEffect() hook, I am basically trying to add an 'id' to each 'item'object mapped to tempData, by incrementing the lastIndex state in each iteration. However, all the item.id s that have been mapped returned 0 (the initial state value).
I am guessing there is something wrong with invoking the setLastIndext function in the iterations? Thanks.
const SearchAppointments = React.memo(() => {
const [data, setData] = useState([ ])
const [lastIndex, setLastIndex] = useState(0)
useEffect( () => {
const fetchData = async() => {
var response = await fetch('../data.json');
var result = await response.json()
var tempData = result.map( item => {
item.id = lastIndex;
setLastIndex(lastIndex => lastIndex + 1);
return item
})
setData(tempData)
};
fetchData();
}, [])
return (
<div>
</div>
);
})
setLastIndex is async function the value of lastIndex will only be updated in next render, but result.map is sync function ==> lastIndex always 0 in result.map
You can try this:
const SearchAppointments = React.memo(() => {
const [data, setData] = useState([ ])
// You not really need this lastIndex state for setting id for your data item, but somehow you want it after setData you can keep this and set to the last index of the item in fetched data
const [lastIndex, setLastIndex] = useState(0)
useEffect( () => {
const fetchData = async() => {
var response = await fetch('../data.json');
var result = await response.json()
var tempData = result.map( (item, index) => ({...item, id: index}))
setLastIndex(tempData.length -1)
setData(tempData)
};
fetchData();
}, [])
return (
<div>
</div>
);
})
You will try this:
const SearchAppointments = React.memo(() => {
const [data, setData] = useState([]);
const fetchData = async() => {
const response = await fetch('../data.json');
const result = await response.json();
setData(result.map( (item, index) => ({...item, id:index})))
};
useEffect( () => {
fetchData();
}, [])
return (
<div>
</div>
);
})```
Do you really need lastIndex?
There's data.length.
It can be used inside setData(lastData=>...)

Resources