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>
}
Related
I try to follow some tutorial on youtube, and trying to build an admin dashboard
the tutorial uses some dummy data for the portal
export const ordersData = [
{
OrderID: 10248,
CustomerName: 'Vinet',
},
{
OrderID: 345653,
CustomerName: 'Carson Darrin',
}, etc...
i try to replicate with the real API interaction
export const ordersData = fetch('API_URL', {method:"GET"})
.then(res => {
return res.json();
}).then(data => {
console.log(JSON.parse(data.body))
return JSON.parse(data.body)
})
but its seems not to work as expected, i try to compare the console.log with the directly input data
they look the same in the console, both of them are shown as objects (typeof).
not sure which step is going wrong
The problem is here you want to return from fetch browser API, which returns Promise. If you really want to do it, for instance, instead of using useState hook in React to call and save the response in component state, then you need to wrap your fetch request into async IIFE function and use await statement to wait the async request to be fulfilled and the data to be resolved by Promise. For more info, you can refer: JS Fetch API access return value
write your fetch in an useEffect and set its response in a state
const [data, setData] = useState()
useEffect(() => {
fetch('API_URL', {method:"GET"})
.then(res => {
return JSON.parse(res);
}).then(response => {
setData(response)
})
}, [])
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.
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.
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>;.
What's the best approach to using the results of one fetch request to make another fetch request to a different endpoint? How can I confirm the first fetch has completed and setState has happened?
class ProductAvailability extends React.Component {
state = {
store_ids: []
}
componentDidMount() {
fetch(`myapi.com/availability?productid=12345`)
.then((results) => {
return results.json();
})
.then((data) => {
const store_ids = data.result.map((store) => {
return store.store_id
})
this.setState({store_ids: store_ids})
})
/* At this point I would like to make another request
to myapi.com/storedata endpoint to obtain information
on each store in the state.store_ids, and add details
to the state */
}
render() {
return (
<div>
<p>STORE INFO FROM STATE GOES HERE</p>
</div>
)
}
}
When you do setState, it updates the component, so the natural point to read state after you've done a setstate, if you'd have read the docs, is in componentDidUpdate(prevProps, prevState).
I leave you to the doc: https://reactjs.org/docs/react-component.html#componentdidupdate
Attention: Don't use willupdate, it's unsafe as you read in the docs.
A further consideration could be done. If you could avoid to put these data in the state, you could also do everything in the componendDidMount (with promiseall for all the other requests maybe) and then set the state with the old and new data, this is preferable since you update your component only once.
Easier to do with async/await (.then/.catch requires a bit more work -- also, reference to Bluebird's Promise.each function). For clarity, its best to move this out of componentDidMount and into its own class method.
As a side note, if this action is happening for every product, then this secondary query should be handled on the backend. That way, you only need to make one AJAX request and retrieve everything you need in one response.
componentDidMount = () => this.fetchData();
fetchData = async () => {
try {
const productRes = fetch(`myapi.com/availability?productid=12345`) // get product data
const productData = await productRes.json(); // convert productRes to JSON
const storeIDs = productData.map(({store_id}) => (store_id)); // map over productData JSON for "store_ids" and store them into storeIDs
const storeData = [];
await Promise.each(storeIDs, async id => { // loop over "store_ids" inside storeIDs
try {
const storeRes = await fetch(`myapi.com/store?id=${id}`); // fetch data by "id"
const storeJSON = await storeRes.json(); // convert storeRes to JSON
storeData.push(storeJSON); // push storeJSON into the storeData array, then loop back to the top until all ids have been fetched
} catch(err) { console.error(err) }
});
this.setState({ productData, storeData }) // set both results to state
} catch (err) {
console.error(err);
}
}