I'm trying to display a list of Item from an API call to a list of components.
Here's my code:
function Content({...props}) {
const [list, setList] = useState([])
const [loading, setLoading] = useState(true)
const [components, setComponents] = useState([])
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
setComponents([])
setList(data)
console.log(data)
})
})
useEffect(() => {
if (components.length > 0) {
return;
}
let tmp = [...components];
for (const elem in list) {
const info = list[elem]
API.getUserById(info.userid, (data) => {
tmp.push(<InfoItem id={info._id} key={info._id} info={info} module={info.module} since="N/A" user={data.initial ? data.initial : `${data.firstname} ${data.lastname}`} {...props}/>)
setComponents(tmp)
console.log(tmp)
})
}
}, [list])
console.log(components)
return(
<div className="container-fluid">
<div className="row">
<CardHeader title="My tittle"/>
<div className ="col-lg-12">
{loading ?
<Card content={"Loading..."}/>
:
<Card content={
<div style={{height: "62vh", overflow: "hidden"}}>
<div className="list-group h-100" style={{overflowY: "scroll"}}>
{components ? components : <p>Nothing</p>}
</div>
</div>
}/>
}
</div>
</div>
</div>
)
}
As you can see I use one useEffect to handle the result from the API and another one to update the components list. But when I display Content, it's always missing one or many item from the list, even when the list have only 2 elements. And when I display tmp, it's contain all the components as well as when I display the components list. I don't know why but it seems that the update of setComponents doesn't affect the return.
If I try to add some fake elements and fast reload, all the component are poping, I don't know how to force update the list component.
If someone know where that missing elements can came from it will be great. thank you.
I think you need to wait for the async task to finish. Try to fit an await or a .then in the API.getUserById. Your data probably has not yet been retrieved by the time the setComponents(tmp) is executed.
The error is because the tmp array stay the same, even when new item are push so the setComponents doesn't render because it's still the same array, here's what I've done to fix that:
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
let all = []
for (const elem in data) {
const info = data[elem]
API.getUserById(info.patientid, (data) => {
let tmp = [...all]
tmp.push(<InfoItem id={info._id} key={info._id} info={info} module={info.module} since="N/A" patient={data.initial ? data.initial : `${data.firstname} ${data.lastname}`} {...props}/>)
all.push(tmp[tmp.length - 1])
setComponents(tmp)
console.log(tmp)
})
}
})
})
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
setComponents([])
setList(data)
console.log(data)
})
},[]);
Related
Been trying to print out a simple list from db for 2 days now, here's the code right now:
function CategoriesTable() {
const [isLoading, setLoading] = useState(true);
let item_list = [];
let print_list;
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then((response) => {
const category_list = response.data.result;
if(category_list) {
for(let i = 0; i < category_list.length; i++){
item_list.push(category_list[i].category_name)
}
}
print_list = function() {
console.log(item_list.map((item) => <li>item</li>))
return item_list.map((item) => <li>item</li>)
}
setLoading(false);
})
}, [])
return (
<div>
{ !isLoading && print_list }
</div>
)
}
I think the function should be executed after the loading state gets changed to false, right? For some reason the function is not executing
By the way, I can print out the list in console without a problem, rendering the list is the problem.
I would suggest to do something like this:
function CategoriesTable() {
const [fetchedData, setFetchedData] = useState({
result: [],
isLoading: true,
});
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then(response => {
const category_list = response.data.result;
setFetchedData({
isLoading: false,
result: category_list.map(category => <li>{ category.category_name }</li>),
})
})
}, [])
return (
<div>
{ !fetchedData.isLoading && fetchedData.result }
</div>
)
}
Basically rewrite from the ground up since the original code is quite messy I'm afraid.
Feel free to ask in the comments if you have any questions.
import { useState, useEffect } from "react";
import Axios from "axios";
/**
* Why mix cases here? Are you gonna use camel or snake case? Choose one and only one.
*/
function CategoriesTable() {
const [categoryNames, setCategoryNames] = useState([]);
useEffect(() => {
// not a good idea to use full URL on localhost
Axios.get('/categories').then((response) => {
const categoryList = response.data.result;
if (categoryList) {
const categoryNames = categoryList.map(({ category_name }) => category_name);
console.log(categoryNames); // in case you want ot keep the console output
setCategoryNames(categoryNames);
}
});
}, []);
return (
<div>
{/* You should either wrap <li> with either <ol> or <ul> */}
<ol>
{categoryNames.map(categoryName => (
// key is required and should be unique in map statement. In here I assume there are no duplicated categoryName
<li key={categoryName}>{categoryNames}</li>
))}
</ol>
</div>
);
}
I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])
I have created a form and I have noticed that when I submit data, they are not writing in the db (with error 400). So I have investigated and I have noticed that one api call that I make in useEffect is done about 5 time during the submit. (I have tried to comment this part and It works!)
I have a first part of form, in which with a select I make a choose, this value is used to make an api call (and there is the problem) to give back some data to use in the form.
return (
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="client-application"
data-cy="application"
type="select"
className="form-control"
name="application"
onChange={handleChangeApp} // there i save the value applicationApp
required
value={applicationApp}
>
<option value="" key="0">
Select
</option>
{applicationListAPP ?
applicationListAPP.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDescription}
</option>
);
})
: null}
</AvInput>
</AvGroup>
<ShowRoleApp applicationRole={applicationApp} /> // so there I pass the value to make the api call
)
const ShowRoleApp = ({ applicationRole }) => {
const [profili, setProfili] = useState([]);
const [isLoading, setIsLoading] = useState(false);
if (!applicationRole) {
return <div />;
}
// I think that it the problem, because it recall GetProfili
useEffect(() => {
async function init() {
await GetProfili(applicationRole)
.then((res) => {
console.log('res ', res);
setProfili(res);
setIsLoading(true);
})
.catch((err) => console.log('err ', err));
}
init();
}, []);
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
const GetProfili = async (appCod) => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
const result = res.clone().json();
return result;
};
const RenderProfili = (profili, applicationRole) => {
const ruoliOperatore = profili ? profili.filter(it => it.appCod.toString() === applicationRole.toString()) : null;
return (
<AvGroup>
<Label for="sce-profiloutentepa-pucCod">Profile (*)</Label>
// other code for the form...
So in your opinion how can i do to call the GetProfili without recall every time when I submit the form?
Thank you
You could define GetProfili as a custom hook an manage the useEffect call in it.
It will return the isLoading and profili instances.
Try to change your code like this.
GetProfili:
const GetProfili = (appCod) => {
const [isLoading, setIsLoading] = useState(true)
const [profili, setProfili] = useState([])
const loadProfili = async () => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
setProfili(res.json())
setIsLoading(false)
}
useEffect(() => {
loadProfili()
}, [])
return { isLoading, profili };
};
ShowRoleApp:
const ShowRoleApp = ({ applicationRole }) => {
if (!applicationRole) {
return <div />;
}
const { isLoading, profili } = GetProfili(applicationRole)
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
I didn't really understand the question but I can say something that might help. The useEffect() hook gets called on every rerender of the component so if it updates 5 times its because some states inside the component get updated 5 times. Also states are updated in child components update the parent.
I have a question. I have a component that when entering /category/:categoryId is rendered doing a fecth to "api url + categoryId". My problem is that if I change from one category to another the page only changes if the useEffect is executed infinitely which generates problems to the view as seen below. How can I make it run once and when I change from /category/1 to /category/2 the useEffect is executed correctly?
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;
My router file:
<Route path="/category/:categoryId" component={Categories} />
This is the problem that is generated, there comes a time when a fetch is made to a previously requested category and then the new requested category is executed.
See my problem in video
You can simply add categoryId to useEffect array argument. Function inside the useEffect is called, only when categoryId changes
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[categoryId]);
you can not edit producto directly, you should use productos :
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto && producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;
The idea is to fetch cards that user used in the database and compare with the all cards that are listen on screen. Basically user is not supposed to see cards which he already used.
const loadSaveInvestments = async (
_req: express.Request,
res: express.Response
) => {
const findSaveInvestments = await prisma.investmentSave.findMany({
select: {
investment_id: true,
},
});
if (!findSaveInvestments) throw new HttpException(400, SAVE_INVEST_MISSING);
res.send(findSaveInvestments);
};
So, I'm sending all of the saved investment cards. Now, in the React - I'm fetching the sent data from the service (all cards and saved cards)
const fetchUserSaveInvestments = async () => {
const res = await axios.get(`${baseURL}/investments-save`);
return res;
};
const [cards, setCards] = useState([]);
const [saveCards, setSaveCards] = useState([]);
useEffect(() => {
setLoading(true);
investService.fetchUserSaveInvestments().then((res) => {
setSaveCards(res.data);
});
investService.fetchCards().then((res) => {
setCards(res.data.investments);
setLoading(false);
});
Now - the main part. I want to map card only if it's id is not in the save cards state, so I've tried something like that.. I've marked the point where I got kinda stuck, I mean, am I supposed to create something like double map or what? Idk if I'm going in the right direction.
<div className={classes.CardsContainer}>
<div className={classes.CardsSelector}>
{loading && <p> its loading </p>}
{!loading && (
<>
{cards
.filter(
(card) => card.id !== saveCards.?????.investments_id
)
.map((card) => (
<Card
{...card}
handleClick={() => handleChooseCard(card)}
/>
))}
</>
)}
</div>
Thanks in advance.
I'd try using a Set to keep track of the saved card IDs since it provides O(1) time complexity lookups (fast, better than searching another array for each filter candidate).
const [loading, setLoading] = useState(false)
const [cards, setCards] = useState([])
const [saveCards, setSaveCards] = useState(new Set())
useEffect(async () => {
setLoading(true);
const { data: savedCards } = await investService.fetchUserSaveInvestments()
const savedCardIds = savedCards.map(({ investments_id }) => investments_id)
setSaveCards(new Set(savedCardIds))
const { data: { investments: cards } } = await investService.fetchCards()
setCards(cards)
setLoading(false)
}, [])
You can then use a memo to do the filtering
const myCards = useMemo(() => cards.filter(({ id }) => !saveCards.has(id)), [cards, saveCards])
{myCards.map(card => (
<Card
{...card}
handleClick={() => handleChooseCard(card)}
/>
))}
Simply declare the following state:
const [investmentsIds, setInvestmentIds] = useState([]);
Add few more lines of code here:
investService.fetchUserSaveInvestments().then((res) => {
setSaveCards(res.data);
let savedCardIds = [];
/* push investment_id in an array */
res.data.forEach((value)=>{
savedCardIds.push(value.investments_id);
})
setInvestmentIds(savedCardIds);
});
Your JSX:
<div className={classes.CardsContainer}>
<div className={classes.CardsSelector}>
{loading && <p> its loading </p>}
{!loading && (
{cards.map((card) => (
{investmentsIds.includes(card.id) &&
<Card {...card}
handleClick={() => handleChooseCard(card)}/>
}
))}
)}
</div>
</div>