Reputation: 57
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)
Upvotes: 0
Views: 847
Reputation: 802
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
}, []);
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
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]);
Upvotes: 1
Reputation: 150
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.
Upvotes: 1
Reputation: 281
dogBreedsTest?.map()
dogBreedsTest
is an arrayretrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
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])
Upvotes: 0