I have created following reactComponent. From what I understand useState([]); should set the comments variable to an array. But it does not. I get an exeption that I Try to run .map() on an object. What I have to do is Object.values(comments) to make it work but I don't understand why. My api definetly returns an array of comment objects.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default ({ postId }) => {
const [comments, setComments] = useState([]);
const fetchData = async () => {
const res = await axios.get(
`http://localhost:4001/posts/${postId}/comments`
);
setComments(res.data);
};
useEffect(() => {
fetchData();
}, []);
const renderedComments = comments.map(comment => {
return <li key={comment.id}>{comment.content}</li>;
});
return <ul>{renderedComments}</ul>;
};
Your use of state is correct as far as I can tell, the fact that Object.values works on the data handed back to you implies it is definitely either an object or an array, have you run Array.isArray(res.data) as part of your troubleshooting process?
As stated by a commenter above, the most likely scenario is that you are getting an object back from your back end. Other things you can do to confirm its the data handed back to you at fault would be to simply comment out your useEffect and see if it still throws the same exception.
Related
im having a little issue trying to fetch and filter some data when a component mounts. Basically what I am trying to do is to fetch some random data and then filter it with a condition. but the filtering of the data is not working, there is most likely a part I misunderstood using useEffect. I made a code sample where I simplified and replicated the issue on https://codesandbox.io/s/green-night-rhg4lj?file=/src/App.js
When I press on the button I expect the filtered data to be console logged, but gives me only an empty array, Ive tried to add "filteredData" or "fetchedData" as a dependency of the useEffect, and yes, it does help me getting the filtered data right at the start but goes into an endless loop because of the behaviour of the useEffect dependencies with obj and arrays. Anyone knows of a way to get the data from API/Database and filter it right on the mount without going into a fetch loop?
Here is also the code written beside the codesandbox:
import axios from "axios";
import { useState, useEffect, useCallback } from "react";
export default function App() {
const [fetchedData, setFetchedData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const getData = useCallback(async () => {
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
setFetchedData(data);
}, []);
useEffect(() => {
getData();
(async () => {
await setFilteredData(fetchedData.filter((p) => p.title.length > 20));
})();
}, []);
const clickHandler = () => {
console.log(filteredData);
};
return (
<div className="App">
<button onClick={clickHandler}>Click</button>
</div>
);
}
You almost right!
You were right when putting the request in the useEffect hook.
...
const getData = useCallback(async () => {
const { data } = await axios.get("https://jsonplaceholder.typicode.com/posts");
return data
}, []);
useEffect(async () => {
const dataFromAPI = await getData();
setFilteredData(dataFromAPI.filter((p) => p.title.length > 20));
}, []);
...
Instead updating the state in the getData funtion just return it.
In the useEffect you get that data and the do what ever you want to do there.
note:
According to this it's ok to use async in useEffect
My component relies on local state (useState), but the initial value should come from an http response.
Can I pass an async function to set the initial state? How can I set the initial state from the response?
This is my code
const fcads = () => {
let good;
Axios.get(`/admin/getallads`).then((res) => {
good = res.data.map((item) => item._id);
});
return good;
};
const [allads, setAllads] = useState(() => fcads());
But when I try console.log(allads) I got result undefined.
If you use a function as an argument for useState it has to be synchronous.
The code your example shows is asynchronous - it uses a promise that sets the value only after the request is completed
You are trying to load data when a component is rendered for the first time - this is a very common use case and there are many libraries that handle it, like these popular choices: https://www.npmjs.com/package/react-async-hook and https://www.npmjs.com/package/#react-hook/async. They would not only set the data to display, but provide you a flag to use and show a loader or display an error if such has happened
This is basically how you would set initial state when you have to set it asynchronously
const [allads, setAllads] = useState([]);
const [loading, setLoading] = useState(false);
React.useEffect(() => {
// Show a loading animation/message while loading
setLoading(true);
// Invoke async request
Axios.get(`/admin/getallads`).then((res) => {
const ads = res.data.map((item) => item._id);
// Set some items after a successful response
setAllAds(ads):
})
.catch(e => alert(`Getting data failed: ${e.message}`))
.finally(() => setLoading(false))
// No variable dependencies means this would run only once after the first render
}, []);
Think of the initial value of useState as something raw that you can set immediately. You know you would be display handling a list (array) of items, then the initial value should be an empty array. useState only accept a function to cover a bit more expensive cases that would otherwise get evaluated on each render pass. Like reading from local/session storage
const [allads, setAllads] = useState(() => {
const asText = localStorage.getItem('myStoredList');
const ads = asText ? JSON.parse(asText) : [];
return ads;
});
You can use the custom hook to include a callback function for useState with use-state-with-callback npm package.
npm install use-state-with-callback
For your case:
import React from "react";
import Axios from "axios";
import useStateWithCallback from "use-state-with-callback";
export default function App() {
const [allads, setAllads] = useStateWithCallback([], (allads) => {
let good;
Axios.get("https://fakestoreapi.com/products").then((res) => {
good = res.data.map((item) => item.id);
console.log(good);
setAllads(good);
});
});
return (
<div className="App">
<h1> {allads} </h1>
</div>
);
}
Demo & Code: https://codesandbox.io/s/distracted-torvalds-s5c8c?file=/src/App.js
I have a sidebar which sets a category Id on click and shows product information based on this Id.
In order to display each product's details, I make an api post call using axios in a useEffect() hook and setData with useState hook.
However, when I try to console.log the data, I get unreliable data back, that is, some undefined and some data.
import { useState, useEffect, useCallback, useMemo } from "react";
import axios from "axios";
import ProductCard from "../Product-cards/_medium-product-card";
const ProductDisplay = ({ subCategorySkus, categoryId, allSkus }) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState();
const apiRequest = { product_skus: allSkus };
const productData = useProductData(apiRequest);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.post("/products", apiRequest);
setData(response.data);
setIsLoading(false);
} catch (err) {
console.log(err.message);
}
};
fetchData();
}, [categoryId]);
console.log(data);
return isLoading ? (
" data is loading ..."
) : (
<div className="product-display">
<h3>Child component Id: {categoryId}</h3>
<ProductCard categoryId={categoryId} />
</div>
);
};
export default ProductDisplay;
You can see here what I get in the console:
.
I have tried adding an if condition, but I am not sure that addresses the issue. I have tried with different dependencies in the useEffect. But I feel there is something I am not understanding.
The api is sending the information correctly, but I get several responses in the console.log and some of them come in as undefined.
This means that I can do a data.map because it crashes the component.
Any help?
UPDATE:
#moonwave99 provided the answer (see below). For the benefit of others who may browse through this question and to clarify what the solution entails, I am including an image below, showing where I changed my code. I think that most importantly, I had not initialised my useState() to an empty array, like this: useState([]).
Any further clarification of the issues here by anyone who knows more, is welcomed. As this is only a quick fix without enough context to understand the logic.
I have a NextJS application where I am building a search results page based on a query parameter being passed. I am using the next/router to pick up the query string. The first time the page loads after compilation the querystring gets picked up and the call to the API is successful and returns results. Every subsequent try, whether on a F5 refresh, reloading the page via the reload button, or accessing the page again the call to the API gives no results because the querystring is undefined.
the URL:
http://localhost:3000/search?search=blah
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import axios from 'axios'
export default function SearchResults1(props) {
const { query } = useRouter();
const [searchResults, setSearchResults] = useState({})
const [ getQuery, setQuery] = useState({})
useEffect(() => {
function fetchData(){
setQuery(query.search)
console.log(getQuery)
if (getQuery) {
axios.get(`http://localhost:3001/search/${getQuery}`)
.then((response) => {
setSearchResults(response.data)
})
.catch((error) => {
console.warn(error)
})
}
}
fetchData()
},[getQuery, setQuery])
I tried adding the getQuery and setQuery in the useEffect hook so it would be triggered when there are changes, but i still get undefined whenever i load the page a second time.
I also tried removing the getQuery and setQuery and tried without using state, but I still get the same result if I call the query.search directly - the first time it works, subsequently it does not.
I did notice that if I called {query.search} in the return HTML it always renders - so I am thinking it has something to do with perhaps the API call happening and not waiting for the query to be populated.
return (
<div>{query.search}</div> <!--this always works and shows the correct value-->
)
Any thoughts or suggestions on how I can achieve consistent results and always return the query parameters so I can make a good API call? I'm fairly confident I am missing something obvious.
Instead of parsing it directly with the useRouter(), try to get the router ready state and access it once it is ready in the useEffect.
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import axios from 'axios'
export default function SearchResults1(props) {
const router = useRouter();
const [searchResults, setSearchResults] = useState({})
//const query = router.query; <-- or use this. The point is to access router.isReady in useEffect
useEffect(() => {
if (!router.isReady) return; // <-- only use the query when it is ready.
function fetchData(){
axios.get(`http://localhost:3001/search/${router.query.search}`)
.then((response) => {
setSearchResults(response.data)
})
.catch((error) => {
console.warn(error)
})
}
fetchData()
},[router.isReady]) // <-- use this router.isReady
According to the Next.js, It will be an empty object during prerendering if the page doesn't have data fetching requirements.
https://nextjs.org/docs/api-reference/next/router
This way, we can make sure the router information is ready for use when you access it.
How can I create a component for Axios that I can use in different places with different values ??
I got stuck, what should I do?
This is what I have achieved so far
thank you for Helping
import axios from "axios";
const Axios = (props) => {
const [posttitle, postbody] = useState([]);
const [postuserid, postid] = useState([]);
const fetchData = () => {
const { postbodyapi } = props.postbodyapi;
const postuseridapi = "https://nba-players.herokuapp.com/players/james/lebron";
const getbody = axios.get(postbodyapi);
const getuseid = axios.get(postuseridapi);
axios.all([getbody, getuseid]).then(axios.spread((...allData) => {
const databody = allData[0].data.first_name;
const datauseid = allData[1].config.url;
postbody(databody);
postid(datauseid);
}))
}
useEffect(() => {
fetchData()
}, [])
return (
<div className="App">
{posttitle}
<img src={postuserid} alt="asd"/>
</div>
);
}
export default Axios;
You should create a custom hook.
Create a hook called for example useAxios and hold only the fetching method inside of it, and the return state from that hook should be just data.
you can make it so it takes params like "URL, data, method", or make a few smaller hooks like useAxiosGet, useAxiosPost.
If you make a few smaller it will be easier to read and change something if needed.
Here is how I did it, an example of one specific Axios custom hook, use this for example to see how to build it.
useGetCar.js // custom axsios hook
import axios from 'axios';
const useGetCar = async (url, id) => {
const result = await axios.post(url, {id: id});
return result.data[0];
}
export default useGetCar
car.js // page component that displays data
import useGetCar from "#hooks/useGetCar";
let car_id = 1; // some that i send to api
// this function here is not exact from my code,
//but I just wanted to provide you an example.
// I didn't include my original code because it is
//from next.js app and I don't want to confuse u with that
async function getData() {
let car = await useGetCar(`http://localhost/get_car.php`, car_id);
return car;
}
Hope you understood what I'm saying, and I did not confuse you.
Feel free to ask anything if you don't understand something clearly.
Happy coding.