Load props before using to set state in a useEffect hook - javascript

I am trying to pass a user address into this Fetch Function, set the value of a state variable equal to the address, and then use that address to make an api call. But as expected, everything runs at the same time and the api call fails because it does not receive the user address.
I am relatively new to useEffect, the below is how I assume a function like this should be written, but evidently I am missing something. It does not return any errors, just a undefined value in the log statement I have below.
const Fetch = (props) => {
const api_key = process.env.REACT_APP_API_KEY;
const [addr,setAddr] = useState([])
const [data,setData] = useState([])
useEffect(() => {
async function Get(){
setAddr(props.useraddress)
}
Get();
}, []);
async function GetNFT() {
useEffect(() => {
axios
.get(
`https://flow-testnet.g.alchemy.com/v2/${api_key}/getNFTs/?owner=${addr}&offset=0&limit=10`
)
.then(res=> {
setData(res.data.nfts);
})
.catch(err=> {
console.log(err);
})
},[]);
}
GetNFT();
console.log(data);
return (
<div>
<script>{console.log('Fetch'+addr)}</script>
{/*
<>
{data.map((dat,id)=>{
return <div key={id}>
<FetchData NFTData={dat} />
</div>
})}
</>
*/}
</div>
)
}

You need a single useEffect that would depend on useraddress that you can destructure from the props, and make an api call that uses the useraddress. You don't need to store useraddress in the state.
const api_key = process.env.REACT_APP_API_KEY
const createUrl = addr => `https://flow-testnet.g.alchemy.com/v2/${api_key}/getNFTs/?owner=${addr}&offset=0&limit=10`
const Fetch = ({ useraddress }) => {
const [addr,setAddr] = useState([])
const [data,setData] = useState([])
useEffect(() => {
axios.get(createUrlcreateUrl(useraddress))
.then(res=> {
setData(res.data.nfts)
})
.catch(err=> {
console.log(err)
})
}, [useraddress])
console.log(data)
return (
// jsx
)
}
Note that the useEffect would be triggered on component's mount, and whenever useraddress changes. If useraddress might be empty or undefined when the component mounts, add a condition inside that avoids the call:
useEffect(() => {
if(!useraddress) return // skip the api call if the address is empty/undefined/null
axios.get(createUrlcreateUrl(useraddress))
.then(res => {
setData(res.data.nfts)
})
.catch(err => {
console.log(err)
})
}, [useraddress])

Related

How manage data when its already fetched by Axios

I am using a database with MySQL and getting it using Axios and a useEffect. Then I pass my database data to a component using a prop. Like this:
const Component = () => {
//DB
const urlProxy = "/api/cities";
//Hooks
const [data, setData] = useState([]);
//DB Fetch
const fetchData = async () => {
await axios
.get(urlProxy)
.then((res) => {
setData(res.data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
return () => {
fetchData();
};
}, []);
return (
<>
<h1>Cities</h1>
<Cities api={data} />
</>
);
};
Inside of Cities Component I want to make an algorithm to manipulate that data, but I get one empty array (from const [data, setData] = useState([]). After a moment I get the fetched data from database.
const Cities = (api) => {
console.log(data) // First Print: [{api:Array(0)}] then [{api:Array(2)}]
return(
<>
...
</>
)
}
So if it prints at first an empty array I would get an error
I was thinking of using a useTimeout() but i don't know if there is a better solution, in order to use data after it's fetched.
All you would need to do is manipluate the data before you set it into your state
and the best way to wait until that is done is to have a loading state that waits for your data to be pulled and then have your useEffect manipulate it.
Something like this should work for you
const urlProxy = "/api/cities";
const Component = () => {
const [data, setData] = useState();
const [loading, setLoading] = useState(true);
//DB Fetch
const fetchData = async () => {
await axios
.get(urlProxy)
.then((res) => {
// Manipulate your data here before you set it into state
const manipluatedData = manipulateData(res.data)
setData(manipluatedData);
})
.catch((err) => {
console.log(err);
})
.finally(() =>
setLoading(false);
})
};
useEffect(() => {
return () => {
fetchData();
};
}, []);
if(loading){
return 'loading....'
}
return (
<>
<h1>Cities</h1>
<Cities api={data} />
</>
);
};

Uncaught (in promise) Error: Rendered more hooks than during the previous render

enter image description hereI'm using useEffect hook to use fetch api but it doesn't work. when there is only one api it is working fine but when i use another api to fetch data using the useEffect hook in the createData function it gives error.
I did some research and i think it is because of some issues caused in re rendering of component in react, i tried to search for the fix but couldn't find it so I'm posting it, if there is any question kindly ask me in comments I'll give more details about it.
export default function Unpaid({ transporterId, getFn }) {
const [itemData, setItemData] = useState([]);
const [resData, setResData] = useState([]);
const idUrl =
"https://url...";
useEffect(() => {
let mounted = true;
fetch(idUrl)
.then((data) => data.json())
.then((data) => setResData(data));
return () => (mounted = false);
}, []);
console.log(resData, "response data");
const dispatchId = resData.map((item) => item.id);
console.log(dispatchId, "dispatch id");
function createData(
po,
id
) {
useEffect(() => {
fetch(
"https://url+id"
)
.then((data) => data.json())
.then((data) => setItemData(data));
}, []);
console.log(itemData, "yohohoho");
return {
po,
};
}
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
return (
<>
jsx content
</>
);
}
const rows = resData.map((item) =>
createData(
item.purchase_order_details.po_number &&
item.purchase_order_details.po_number.length > 0
? item.purchase_order_details.po_number
: "NA",
item.id
)
);
return (
<>
jsx content
</>
);
}
By calling createData inside resData.map(...), you are calling useEffect in a loop, which violates the rules for hooks. You can call hooks only on the top level.
To fix this, you should move the effect outside of createData to the top level, add the dependency resData to the and loop over resData inside the effect.

Display no results component when there are no results

I'm trying to show the no results components whenever the api has finished loading and when no results are returned. The issue I'm having, is I am seeing the no results components displayed first for a few seconds and then the results showing whenever the api returns data. I should never want to see the no results component showing if there are results returned from the api.
import React, { useEffect, useState } from 'react';
import NoResults from './NoResults';
const Users = () => {
const [results, setResults] = useState([]);
const [isResultsLoading, setIsResultsLoading] = useState(true);
const isLoading = () => {
if (isResultsLoading) return <ResultsLoader />;
if (results && results.length > 0)
return (
<UserTableWrapper>
<UserTable
data={results}
/>
</UserTableWrapper>
);
return <NoResults heading="No users available." />;
};
useEffect(() => {
let isMounted = true;
const getData = async () => {
if (isMounted) {
const users = await fetchUsers(); // is just an api call
if (users && users.length > 0) return { users, loading: false };
return { users: null, loading: true };
}
return { users: null, loading: true };
};
getData().then(({ users, loading }) => {
if (isMounted) {
if (users) setResults(users);
setIsResultsLoading(loading);
}
});
return () => {
isMounted = false;
};
}, []);
return (
<>
<h1>Users</h1>
{isLoading()}
</>
);
};
};
export default Users;
Check for the length of results with results.length since results already exists as an empty array.
When you get your data and parse it simply set both the states for results with the data, and isLoading to false.
Perhaps rename the isLoading function to something more representative of what the function does. I've called mine getJSX.
Here's a working example that uses a mock API. (Note I've had to use this without async and await because snippets haven't caught up with the latest Babel version.) You can change the JSON that's returned by the API by uncommenting/commenting out the JSON statements in the first couple of lines.
const {useState, useEffect} = React;
const json= '[1, 2, 3, 4]';
// const json = '[]';
function mockApi() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 3000);
});
}
function Example() {
const [results, setResults] = useState([]);
const [isResultsLoading, setIsResultsLoading] = useState(true);
function getJSX() {
if (isResultsLoading) return <div>Loading</div>;
if (results.length) {
return results.map(el => <div>{el}</div>);
}
return <div>No users available.</div>;
};
useEffect(() => {
mockApi()
.then(res => JSON.parse(res))
.then(data => {
setResults(data);
setIsResultsLoading(false);
});
}, []);
return <div>{getJSX()}</div>
};
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You should rely on conditional rendering and simplify your logic a little bit.
import React, { useEffect, useState } from "react";
import NoResults from "./NoResults";
const Users = () => {
// This holds the results - default to null till we get a successful API response
const [results, setResults] = useState(null);
// This should be a boolean stating if the API call is pending
const [isResultsLoading, setIsResultsLoading] = useState(true);
useEffect(() => {
const getData = async () => {
const users = await fetchUsers(); // is just an api call
if (users && users.length > 0) {
// if the result is good, store it
setResults(users);
}
// By the way, the api call is finished now
setIsResultsLoading(false);
};
getData();
}, []); // no deps => this effect will run just once, when the component mounts
if (isResultsLoading) {
// Render nothing while API call is pending
return null;
} else {
if (results) {
// The API has returned a good result, so render it!
return (
<UserTableWrapper>
<UserTable data={results} />
</UserTableWrapper>
);
} else {
// No good result, render the fallback component
return <NoResults heading="No users available." />;
}
}
};
export default Users;
You've a lot of extraneous conditionals and code duplication (not as DRY as it could be). Try cutting down on the user checks before you've even updated state, and you likely don't need the mounted check. Conditionally render the UI in the return.
const Users = () => {
const [results, setResults] = useState([]);
const [isResultsLoading, setIsResultsLoading] = useState(true);
useEffect(() => {
fetchUsers() // is just an api call
.then(users => {
setResults(users);
})
.catch(error => {
// handle any errors, etc...
})
.finally(() => {
setIsResultsLoading(false); // <-- clear loading state regardless of success/failure
});
}, []);
return (
<>
<h1>Users</h1>
{isResultsLoading ? (
<ResultsLoader />
) : results.length ? ( // <-- any non-zero length is truthy
<UserTableWrapper>
<UserTable data={results} />
</UserTableWrapper>
) : (
<NoResults heading="No users available." />
)}
</>
);
};

React-Native infinite loop

I am trying to get data from my firebase-firestore I an showing a loading state to wait for the data to load however when it does load it keeps returning the firestore data infinite times. Please may someone help me.
This is my code Paper is just a custom component
import Paper from '../Components/Paper'
import firebase from 'firebase'
import { useState } from 'react'
const Home = (props) => {
const renderMealItem = (itemData) =>{
return (
<Paper
title={itemData.item.name}
serves={itemData.item.servings}
time={itemData.item.time}
image={itemData.item.imageUri}
/>
)
}
const [loading, setLoading] = useState(false)
const [all, setAll] = useState([])
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
},[])
const checkReturn = () => {
if(all !== undefined){
setLoading(false)
}
}
const getUser = async() => {
try {
await firebase.firestore()
.collection('Home')
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
});
}catch(err){
console.log(err)
}
}
return(
<View style={styles.flatContainer}>
<FlatList
data={all}
keyExtractor={(item, index) => index.toString()}
renderItem={renderMealItem}/>
</View>
)
}
useEffect without second parameter will get executes on each update.
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
})
so this will set the loading and tries to get the user. and when the data comess from server, it will get runned again.
So you should change it to : useEffect(() => {...}, []) to only get executed on mount phase(start).
Update: you should check for return on every update, not only at start. so change the code to:
useEffect(() => {
setLoading(true)
getUser()
}, [])
useEffect(() => {
checkReturn()
})
Ps: there is another issue with your code as well:
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
maybe it should be like :
setAll(querySnapshot.docs.map(doc => JSON.stringify(doc.data())));
Try passing an empty array as an argument to useEffect like so:
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
}, [])

React useEffect hook does not fire when prop dependency changes

I am using a useEffect hook to make an API call depending on data from a photos prop being passed in.
const ImageCarousel = ({ src, photos }) => {
const [photoList, setPhotos] = useState([]);
const storageRef = firebase.storage().ref();
console.log("photos prop:", photos);
const getImage = photoId => {
return new Promise((resolve, reject) => {
storageRef
.child("images/" + photoId)
.getDownloadURL()
.then(url => resolve(url))
.catch(error => reject(error));
});
};
useEffect(() => {
console.log("trigger");
Promise.all(
photos.map(async photoId => {
const url = await getImage(photoId);
return url;
})
)
.then(photoUrls => {
setPhotos(photoUrls);
})
.catch(e => console.log(e));
}, [photos]);
return (...)
}
I have passed the photos prop into the dependency array of my useEffect hook, so it should fire when the photos prop changes. I can see that the prop is indeed changing in the console, but my useEffect hook does not fire when the prop change comes through. This is what appears in my console:
Why is the useEffect not firing twice? From my understanding it should fire when the component renders the first time, and fire again every time the photos prop changes.
Try to make sure your photos prop is immutable,
meaning, you send a new array each time there is a change and not mutating the array
This may not be the most correct answer, but I've tried this and it works. Hat tip to https://dev.to/stephane/how-to-make-sure-useeffect-catches-array-changes-fm3
useEffect(() => {
console.log("trigger");
Promise.all(
photos.map(async photoId => {
const url = await getImage(photoId);
return url;
})
)
.then(photoUrls => {
setPhotos(photoUrls);
})
.catch(e => console.log(e));
}, [JSON.stringify(photos])); // stringify the array used as the trigger, and it'll catch the change
useEffect will update if you pass the new data, find the sample code below and check the console.log.
import React, { useState, useEffect } from "react";
const EffectCheck = ({value}) => {
console.log('Component Trigger', value);
useEffect(() => {
console.log('Updated Effect', value);
},[value]);
return (
<div>{value}</div>
)
}
export default function() {
const [checkValue, setCheckValue] = useState(Math.random());
const [dynamicCheckValue, setdyamicCheckValue] = useState(Math.random());
return (
<>
<div><h3>No Update</h3>
<EffectCheck value={checkValue}/>
</div>
<div><h3>New Update</h3>
<EffectCheck value={dynamicCheckValue}/>
</div>
<button onClick={() => setCheckValue('Math.random()')}> No Update</button>
<button onClick={() => setdyamicCheckValue(Math.random())}> New Update</button>
</>
);
}

Categories

Resources