Handling promises with fetch() in React? - javascript

I'm creating my first MERN stack application, and trying to implement a simple API that calls my express server from my React front-end components. I have the API working on the back end, and it is sending the data correctly through fetch(), but I'm having trouble resolving the promise from fetch() in my React component, with the call not stopping firing. My code looks as follows (assuming as of right now all API calls return a dummy format like { title: 'foo', ... }:
import React, { useState } from 'react';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
const getApiData = async (route) => {
try {
let apiData = await fetch(route);
let apiDataJson = await apiData.json();
return apiDataJson;
} catch (err) {
throw new Error('Error on fetch', {
error: err
})
}
}
var retrieve_data = async (route, setterCallback) => {
await getApiData(`/api/${route}`).then((data) => {
console.log('Data retrieved from API')
setterCallback(<div>{data.title}</div>)
}).catch(() => {
setterCallback(<div>ERROR</div>)
})
}
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
let data = retrieve_data('myEndpoint', setinnerDiv);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}
When I compile the above the component successfully renders (i.e. <MyComponent /> looks like:
<div>
<h1>Data Retrieved in MyComponent:</h1>
<div>foo</div>
</div>
However, then then block keeps executing (i.e. the 'Data retrieved from API' logs to the console hundreds of times/second until I close the application. How can I stop this from executing once it has set the component? Thanks!

You need to useEffect to stop the component from re-rendering. Try something like this.
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
useEffect(() => {
retrieve_data('myEndpoint', setinnerDiv);
}, []);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}

Related

Infinite console log in react js component

I have made two simple straight forward component is React, used a open source API to test API integration. React is showing this weird behavior of infinite console logs. I don't understand the issue. I'm using the fetch function for making API calls and functional component.
App component:
function App() {
const [characters, setCharac] = useState([])
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
return (
<div className="App">
{characters.map(charac => {
return <Character {...charac} />
})}
</div>
);
}
Character component:
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
}
console.log('Test'); in App component and console.log(props); in Character component are being executed infinitely.
This is the render method
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Your components are rendering multiple times because your state is changed every time you fetch data (because of setState).
Try creating a function fetchData(). Make this function async as well to wait for data to be retrieved.
const fetchData = async () => {
const result = await fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
return data;
}));
return result;
}
and then use it inside useEffect (Read more about useEffects: React hooks: What/Why `useEffect`?)
useEffect(() => {
fetchData();
}, []);
Note the usage of [] in useEffect. The data will be fetched only once when you load the component.
Try wrapping it in a useEffect
e.g.
useEffect(()=>{
fetch(URL + "people").then(response => response.json().then(data => {
setCharac(data.results)
console.log('Test');
}))
},[])
otherwise every time the state is set it is firing off the fetch again because a re-render is being triggered.
Because you fetch some data, update the state, which causes a re-render, which does another fetch, updates the state, which causes another render...etc.
Call your fetch function from inside a useEffect with an empty dependency array so that it only gets called once when the component is initially rendered.
Note 1: you can't immediately log the state after setting it as setting the state is an async process. You can, however, use another useEffect to watch for changes in the state, and log its updated value.
Note 2: I've used async/await in this example as the syntax is a little cleaner.
// Fetch the data and set the state
async function getData(endpoint) {
const json = await fetch(`${endpoint}/people`);
const data = await response.json();
setCharac(data.results);
}
// Call `getData` when the component initially renders
useEffect(() => {
const endpoint = 'https://swapi.dev/api';
getData(endpoint);
}, []);
// Watch for a change in the character state, and log it
useEffect(() => console.log(characters), [characters]);
You can do something like this:
import React, { useState, useEffect, useCallback } from "react";
const Character = (props) => {
console.log(props);
return (
<div key={props.name}>
<h1>{props.name}</h1>
<p>{props.height}</p>
</div>
);
};
export default function App() {
const [characters, setCharac] = useState([]);
const makeFetch = useCallback(() => {
const URL = "https://swapi.dev/api/";
fetch(URL + "people").then((response) =>
response.json().then((data) => {
setCharac(data.results);
console.log("Test");
})
);
}, []);
useEffect(() => {
makeFetch();
}, []);
return (
<div className="App">
{characters.map((charac) => {
return <Character {...charac} />;
})}
</div>
);
}

How i can get suspense to work on react 18?

Hello my code is as follow wnna try suspense with react 18 but not having luck to render Loading....
User.jsx
import React, { useEffect, useState } from "react";
export const User = () => {
const [user, setuser] = useState(null);
useEffect(() => {
const res = fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => setuser(json));
}, []);
return <div>{JSON.stringify(user)}</div>;
};
App.js
import logo from './logo.svg';
import './App.css';
import { Suspense } from 'react';
import { User } from './components/User';
function App() {
return (
<div className="App">
<Suspense fallback={<p>Loading.....</p>}>
<User/>
</Suspense>
</div>
);
}
export default App;
the app renders {} or null but never getting the fallback as expected
Suspense is a very smart paradygm based on fetch-while-render strategy. This means that React Components from now on are able to consume directly asynchronous data. And will render the fallback while the data promise is not resolved.
The only issue is that you can't pass directly a promise, but you need a wrapper that transforms it into a Suspense consumable entity. Basically the React Component wrapped in Suspense tags, will start to try to render continuosly and it expects for a method that throws a new promise until the original promise is not resolved, that's how it knows that it has to keep rendering the fallback. So you need to pass a resource with a very specific shape, that's why you need a wrapper.
Fetching libraries like react-queris or RTK-query will implement the wrapper themselves, so you won't have to care of that part, it's not something you will have to do manually, but still if you'd like to see what's under the hood, this would be a very basic implementation of a Suspense ready fetching library:
import React, { useEffect, useState, Suspense, useRef } from 'react';
export default function App() {
return (
<div className="App">
<Suspense fallback={<p>Loading.....</p>}>
<User />
</Suspense>
</div>
);
}
export const User = () => {
const data = useGetData('https://jsonplaceholder.typicode.com/todos/1');
return <div>{JSON.stringify(data)}</div>;
};
This is the custom hook useGetData I made:
// VERY BASIC IMPLEMENTATION OF A FETCHING LIBRARY BASED ON SUSPENSE
// This is the official basic promiseWrapper they implement in React Suspense Demo:
function wrapPromise(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
//console.log(status);
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
},
};
}
/* Basic fetching function */
const fetcher = async (url) => {
try {
const res = await fetch(url);
const data = await res.json();
await delay(2000);
return data;
} catch (e) {
throw e;
}
};
/* Util to delay loading */
const delay = (d) => new Promise((r) => setTimeout(r, d));
/* HOOK that lets to start the fetch promise only on component mount */
/* It's based on "wrapPromise" utility, which is MANDATORY to return a Suspense consumable entity */
const useGetData = (url) => {
const [resource, setResource] = useState(null);
useEffect(() => {
const _resource = wrapPromise(fetcher(url));
setResource(_resource);
}, []);
return resource?.read();
};
Suspense
The working implementation is HERE
If you want to experiment how a Suspense-ready library works, you can check this example that makes use of the great state-manager valtio which is extremely light weight and easy to use, and suspense-ready. It let's you just pass a promise in your store, and when you try to access it in a component, the component will fallback if wrapped in <Suspense>.
Check it HERE
While you can wrap your own this it is better to use a library like Relay for fetching data or an alternative state manager such as Valtio or Proxily. With Proxily it would look like this:
import "./styles.css";
import { observable, observer, suspendable } from "proxily";
import React, { Suspense } from "react";
const fetcher = {
user: () =>
wait(2000)
.then(() => fetch("https://jsonplaceholder.typicode.com/todos/1"))
.then((response) => response.json())
};
suspendable(fetcher, (f) => f.user);
const state = observable(fetcher);
function App() {
return (
<div className="App">
<Suspense fallback={<span>Loading...</span>}>
<User />
</Suspense>
</div>
);
}
export const User = observer(() => {
return <div>{JSON.stringify(state.user())}</div>;
});
const wait = (t) => new Promise((r) => setTimeout(r, t));
export default observer(App);
You can see it in action here. This article discusses Suspense in the context of concurrent rendering and transitions, demonstrating how to make Suspense part of a transition so that when fetching new data, you can continue to display the old content rather that the fallback content until the new data is fetched.

One fetch request creates many GET requests on server

Here is my code:
import React from "react";
import { useState, useEffect } from "react";
import TutorialList from "./TutorialList";
import PropTypes from "prop-types";
const Home = () => {
const [tutorials, setTutorials] = useState(null);
useEffect(() => {
console.log("Fetching data");
fetch("http://192.168.212.52:8080/api/tutorials/all/")
.then((res) => {
return res.json();
console.log("Getting json from response");
})
.then((data) => {
console.log(data);
setTutorials(data);
});
}, []);
return (
<div className="home">
{console.log(tutorials)}
{tutorials && (
<TutorialList tutorials={tutorials} title={"All tutorials"} />
)}
</div>
);
};
Home.propTypes = {
title: PropTypes.string,
};
export default Home;
I expect this to make 1 get request to the server, which returns the data
that is then rendered with react.
What it actually does is make more than 10
requests to the server, still rendering the data after all the requests
finish. I can see the requests both from the server logs and from the browser
networking dev tools.
However, the Fetching data and Getting json from response logs only execute
once, as expected.
Since fetch requests a resource from the network, it runs asynchronously. This said, if you want to get to run it inside a useEffect it might be that if you wrap it in an async function it should work. However, keep in mind that it's not the argument of this hook itself async but another function that you define inside. For instance,
useEffect(() => {
console.log("Fetching data");
async function retrieveData() {
const json = await fetch("http://192.168.212.52:8080/api/tutorials/all/")
const data = await response.json()
setTutorials(data)
console.log("Got json from response");
}
retrieveData()
}, []);

How to manage with data from fetch promise?

I am beginner in JS and React.
I have a problem:
import React from "react";
import JsonApi from "../../services/jsonApi";
const UserPage = () => {
const jsonApi = new JsonApi(); //it is my class which has methods
//to manage with data(get,post,etc);
const user = jsonApi.getUser(); //returns promise,but i need an object with data!
//promise has such view:
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: Object !!!!i need this data!!!!
console.log(user); //Promise.
/* i know that a i can do so:
user.then((data) => console.log(data));
but,using this way,i can only log!But i need an object with data!
*/
return (
<div className="app">
<h1>{user.name}</h1>
<p>Here are info about users!</p>
</div>
);
};
export default UserPage;
I understand that i need to use await before const user = jsonApi.getUser();
but we can do that only inside async functions.
So,i tried to do that: const UserPage = async () => { }
but i had a mistake:
In order to perform side effects in react you should consider using useEffect hook. After the effect you need to store the data retrieved in react state by using the useState hook. In the end your code would look like below:
import React, { useState, useEffect } from "react";
import JsonApi from "../../services/jsonApi";
const UserPage = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const jsonApi = new JsonApi();
jsonApi.getUser().then((user) => {
setUser(user);
});
}, []);
if (!user) return null;
return (
<div className="app">
<h1>{user.name}</h1>
<p>Here are info about users!</p>
</div>
);
};
export default UserPage;
Keep in mind that the user is not populated until you async getUser resolves, so you have to handle the case where user data are not yet present, either by rendering nothing (null) or by showing some loading state in between.

Waiting for async function in React component & Showing Spinner

Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};

Categories