API call in useEffect returning undefined - javascript

I have a simple functional component, which includes a fetch to a JSON file stored locally.
I am trying to bring in the data from the JSON file, and whilst this should be a simple task, I have come across a problem.
My console log is showing two separate logs - one is an empty object, presumably from the useState definition and the second has the data from the fetch inside it.
Therefore, When I try to do anything with the data I'm fetching, undefined is being returned, So I can't use it. I feel like I'm missing something obvious, but I'm not entirely sure what is going on here. I have tried async/await without success.
What am I missing ?
const Landing = () => {
const [api, updateApi] = useState({});
const getData = () => {
fetch('data.json')
.then((response) => response.json())
.then((data) => updateApi({api: {data}}))
}
useEffect(() => {
getData();
}, []);
console.log(api)
return (
<p>Hey!</p>
)
}

All you need to do is to wrap the return within an if/else block, and return (for example a spinning circle) loading indicator. When it re-renders with the data from the api, it returns the desired representation of the data.

Using async/await syntax you can put your fetch requests within the useEffect hook and set your state similar to how you are currently doing.
export default function Landing() {
const [api, updateApi] = useState({});
useEffect(() => {
const getData = async () => {
const response = await fetch("data.json");
const data = await response.json();
updateApi({ api: { data } });
};
getData();
}, []);
return <div>{JSON.stringify(api)}</div>;
}
If you initialise the state with useState({}) then the value won't be undefined. If however you want to check for that before doing something then an if/else statement as suggested in another answer is suitable or if it is within the component return then a common pattern is return <div>{api && JSON.stringify(api)}</div>;.

Related

What is the correct way to retrieve data from Firebase, perform computations on it, and display it in React (hooks)?

I am trying to retrieve several "game" objects from Firebase Storage, calculate some statistics on them, and then display the game statistics in a table. The outline of my code is as follows:
function calculateTeamStatistics(team) {
// Iterating over all games, looking for team name in file, crunching the statistics
}
useEffect(() => {
async function prepareStatistics() {
// Iterating over all games, identifying teams (there is no list of teams)
calculateTeamStatistics(team)
}
prepareStatistics();
}, []);
useEffect(() => {
async function getAllGames(tournament: string) {
let gameFileReferences: any = [];
let allGames: any[] = [];
const storageRef = storage.ref();
let gameRef = storageRef.child(`${tournament}_results/`).listAll();
await gameRef.then((gameFiles) => {
gameFileReferences = gameFiles.items;
gameFileReferences.forEach((game: any) => {
storageRef
.child(`${tournament}_results/${game.name}`)
.getDownloadURL()
.then((url) => {
axios
.get(url, {
headers: {
Accept: "application/json",
},
})
.then((response) => {
allGames.push(response);
if (lastModifiedText === "" && response.headers) {
setLastModifiedText(response.headers["last-modified"]);
}
})
.catch((error) => console.log(error));
});
});
});
setTournamentGames(allGames);
}
getAllGames(tournamentId);
}, []);
The setLastModifiedText is the only hook that currently works, updating the text in the render() when I refresh in the browser. None of the statistical calculations display, however, unless I change something in the render() and save the file. When I attempt to perform the calculations and iterations inside the .then() call, where the setModifiedText, I run into more issues.
I am having difficulty figuring out what the issue is. I figure one of my async or useEffect() calls is out of place, but do not know where. Thanks for any help you can provide!
I attempted to change various async and useEffect() calls, as well as refactoring my functions, but no combination solved the problem.
I don't know if this is the optimal answer but this pattern worked for me:
const [data, setData] = useState(**your default value**);
const [object, setObject] = useState(**your default value**);
useEffect(() => (setData(**your async call**), []);
useEffect(() => (setObject(data)), [data]);
The key is to make the async call for data in the background, then use another useEffect that has a dependency to the data being retrieved. When that data changes upon completion of the async call, the useEffect with a dependency on the retrieved data will run and force a rerender.

Can't access lexical definition of response before initialization within a React hook

I'm trying to make the home page send one API call on load and display all results on screen. It seems to send the call and receive response fine, although despite receiving the response from the server it can't pass the contents of the payload within the code, which is a JSON.
useEffect(() => {
const localUser = localStorage.getItem("user");
if (localUser) {
const foundUser = localUser;
setUser(foundUser);
} else {
const newUser = uuidv1();
localStorage.setItem(newUser, user);
setUser(newUser);
}
console.log(user);
async function fetchPosts() {
try {
let tempPosts = [];
const response = await fetch('http://localhost:3000/posts')
.then(response => response.json())
.then(response.payload.forEach(object => tempPosts.push(object.post)))
.then(setPosts((posts) => [tempPosts]));
console.log(posts);
} catch (err) {
console.log(err)
}
}
fetchPosts();
}, [user, posts]);
Somehow React is trying to access the response without the declaration and I have no idea how, which in result stops the function from executing.
Take a look at this line:
const response = await fetch('http://localhost:3000/posts')
.then(response => response.json())
You're combining two paradigms - asynchronous programming using Promises and callbacks with then, and asynchronous programming using async/await. You'll usually want to pick one or the other for use in a single function, and you definitely cannot combine them in a single line (or at least, not like this).
If you want to use async (and I would recommend this approach), you'll probably want something like this:
async function fetchPosts() {
let tempPosts = [];
const response = await fetch('http://localhost:3000/posts');
const data = await response.json();
data.payload.forEach(object => tempPosts.push(object.post))
return tempPosts;
}
I don't know what data looks like, so you may have to play with the 4th line of the function, but hopefully you get the gist. You'll probably want to define this function outside of your component, and certainly not within the useEffect hook.
An example of how you could use this to fetch posts on the first render of your component is
useEffect(() => {
fetchPosts().then(data => setPosts(data));
}, []);
assuming you have a relevant useState hook.

Can someone help me understand how async + await + useEffect work in React?

I have a React app built with the Minimal template and I'm trying to follow along with one of their tutorials, in order to create a Redux slice that feeds some data to a custom component. The data itself is collected from Firebase. Below is my code:
firebase.js - helper
export function getDocuments(col) {
const colRef = collection(db, col);
const q = query(colRef, where('uid', '==', auth.currentUser.uid));
getDocs(q).then((snap) => {
const data = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
return data;
});
// return [1,2,3]
}
product.js - Redux slice
export function getProducts() {
return async (dispatch) => {
dispatch(slice.actions.startLoading());
try {
const products = await getDocuments('products');
dispatch(slice.actions.getProductsSuccess(products));
} catch (error) {
dispatch(slice.actions.hasError(error));
}
};
}
ProductList.js - component
const dispatch = useDispatch();
const { products } = useSelector((state) => state.client);
useEffect(() => {
dispatch(getProducts());
}, [dispatch]);
useEffect(() => {
if (products.length) {
// setTableData(products);
}
}, [products]);
If I console log data in the helper function (firebase.js), I get the values I expect, once the promise is resolved/fulfilled. However, if I console.log clients in the product.js slice or later in the component, I get undefined.
I assume my problem is not being able to understand how async + await + useEffect work together in order to fix this. My assumption is that I am trying to access the value before the promise is resolved and therefore before the helper function returns it. I confirmed that by returning a simple array [1, 2, 3] in my helper function as a test.
I think I am missing something fundamental here (I am not very experienced with React and JS in general and still learning things on the go). Can someone help me understand what am I doing wrong?
Thank you!
With await you can await the fulfillment or rejection of a promise, but your getDocuments Function does not return a promise. Change the last line of the function to the following:
return getDocs(q).then((snap) => {
const data = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
return data;
});
Async and Await are no different in React than in plain JavaScript:
When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method
useEffect():
By using this Hook, you tell React that your component needs to do something after rendering. This function will run every time the component is re-rendered.

Check if Object return value from API call, in another file

I have created a js file with API call that return array and I have filter specific filter
API return array:
const apiValues = ['value1', 'value2']
const checkIfExist = () => {
fetch('api/profile')
.then(response => response.json())
.then(result => (
Object.values(response.apiValues).filter(value => value === 'value2');
);
};
export default checkIfExist;
So, if API return 'value2' I need to show a component in another file:
import checkIfExist from './checkIfExist.js'
if(checkIfExist) {
return (
<SpecificChildComponent />
);
}
When I see the page, api is called but HTTP status is 404 Not Found
Your function is not returning a value. You either need to alter your function to return a promise or update it to accept a callback. Depending on your browser support/tooling you can use async/await to simplify the code.
async const checkIfExist = () => {
const response = await fetch('api/profile');
const result = response.json();
return Object.values(response.apiValues).filter(value => value === 'value2');
};
const exists = await checkIfExist();
Its not clear from your example but you should not put this code into the render path of your components. Render is called many times and placing this call in render will block the whole render process every time it rerenders. You should ideally perform this call either componentDidMount or depending on your code just somewhere outside the render path.

ReactJS logic for rendering application

So I am not the best with React. I am building an application that gets user information from an external endpoint and stores it in local storage. I realize that my react-app is loading the html before the state for that data is updated and just doesn't display it in the front end. I would like to have the application wait until those items are in local storage before rendering. Any suggestions?
you can use async and await for that.
async componentDidMount(){
await fetch()....
/* your code goes here*/
}
Using functional components, you would typically do something like:
function App() {
const [data, setData] = useState(null)
useEffect(() => {
// Fetching or other async logic here...
fetch('/some/url/here')
.then(response => response.json())
.then(data => setData) // save the result in state
}, [])
// Not loaded yet, return nothing.
if (!data) {
return null
}
// Loaded! return your app.
return <div>Fetched data: { data.myData }</div>
}

Categories

Resources