I have this working, when the user goes to this page, this line of code executes:
interface Badge {
id: string;
badge_name: string;
badge_description: string;
img64: string;
}
const [data, setData] = useState([] as any[]);
const [isPending, setisPending] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const onSearchChange = (e: any) => {
setSearchTerm(e.target.value);
};
const setDataWithImg = useCallback(async (badges: Badge[]) => {
let badgeWithImg: Badge[] = [];
const base64Flag = "data:image/png;base64,";
await Promise.all(
badges.map(async (badge: any) => {
const imgBuffer = badge.img_icon.data;
const imgBase64 = bufferToBase64(imgBuffer);
badge.imgBase64 = `${base64Flag}${imgBase64}`;
badgeWithImg.push(badge);
})
);
setData(badgeWithImg);
}, []);
const loadData = useCallback(async () => {
console.log("loadData");
try {
setisPending(true);
await BadgeService.loadData().then(
(res) => {
setDataWithImg(res.data);
setisPending(false);
},
(error) => {
setisPending(false);
}
);
} catch (err) {
console.log(err);
setisPending(false);
}
}, [setDataWithImg]);
useEffect(() => {
loadData();
}, [loadData]);
It will load the data from BadgeService.loadData and I have this function also that will search data from api, and this code will execute.
const onClickFilter = async (e: any) => {
e.preventDefault();
if (searchTerm === "") {
loadData();
} else {
try {
console.log("filterData");
setisPending(true);
await BadgeService.filterData({
badge_name: searchTerm,
}).then(
(res) => {
setDataWithImg(res.data);
setisPending(false);
},
(error) => {
setisPending(false);
}
);
} catch (err) {
console.log(err);
setisPending(false);
}
}
};
User has a search function and that code will execute, search function works fine, I want when user click filter with empty value in search, it will load the original loadData. I already tried console.log('Loaddata') to trace if my condition is working fine and it is ok, but when I check the network, it still executing the api call from filterData, not loadData
First load of the page:
When user fires search function:
Where user fires search function but empty search term:
Base on network logs:
The last request should be the badges only, not the with parameters..How I fix this? What Am I missing here?
Thank you!
Looking at your code, your if check in onClickFilter function seems wrong.
you can do something like
if(!searchTerm){
loadData();
}
Doing !searchTerm will return true for every "falsy" value (empty string, 0, null, false, undefined, NaN) whereas x == "" will only return true if x is null (or apparently undefined).
Please let me know if it works.
I got may issue fix by doing this,
I have this code in my service:
const loadData = () => {
config["params"] = {};
return axios.get(API_URL + "api/v1/badges", config).then((response) => {
//console.log("from loaddata..");
//console.log("load data", response);
return response;
});
};
const filterData = (data: any) => {
config["params"] = {
s: data,
};
return axios.get(API_URL + "api/v1/badges", config).then((response) => {
console.log("from filterdata..");
console.log("filter data", response);
return response;
})
;
I just added config["params"] = {}; this line of code to loadData
Thank you all!
Related
I try to get user data when load page using hooks(in case useEffect), but i got error said "Consider adding an error boundary to your tree" and my app getting blank.
my code looks similar like below
still configure out how to fix it, but i dont know how. coz i new using react
const [ProfileData, setProfileData] = useState({});
const [ownerId, setOwnerId] = useState('')
let ProfileID = 12
const getId = async () => {
const responseData = await getUserData();
setOwnerId(responseData.ID);
};
this is my function to get data from API
const getProfileData = () => {
setLoading(true);
getId();
const jsonData = {
ID: ProfileID,
OwnerId: ownerId
};
const headers = {
'Access-Control-Allow-Headers': '*'
};
try {
axios
.post(
config.API_SERVER + 'SearchProfile',
{
Data: jsonData ,
},
headers
)
.then(function (response) {
if (response.data.status == 'Success') {
setProfileData(response.data.Data);
setLoading(false);
} else {
setProfileData({});
alert(response.data.status);
}
})
} catch (error) {
console.log(error);
}
};
this is my hooks
useEffect(() => {
const load = async () => {
try {
await getProfileData();
} catch (err) {
throw err
}
}
load()
}, []);
and this is my return to display data
return (
<p>{ProfileData.Name}</p>
)
Since you throw an error (throw err) but you don't catch it, you can omit that part, pass the error message to state, or use react-error-boundary.
useEffect(() => {
getProfileData();
}, []);
I have my App component defined as below;
function App() {
const [state1, setState1] = useState({});
const [state2, setState2] = useState({});
const [isApiCallDone, setIsApiCallDone] = useState(false);
const fetchFn = useMyCustomFetch();
useEffect(() => {
(async function() {
try {
let [state11] = await fetchFn('api/api1', {}, 'GET');
let [state22] = await fetchFn('api/api2', {}, 'GET');
setState1(state11); // Is there a better way to set this ?
setState2(state22);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
useEffect(() => {
if (Object.keys(state1).length > 0 && Object.keys(state2).length > 0) {
// Set some other state variables on App
}
}, [state1, state2])
return (
<>
<MyContextProvider>
{isApiCallDone && (
<MyComponent />
)
}
</MyContextProvider>
</>
}
Also my useMyCustomFetch hook looks like below
export default function useMyCustomFetch() {
const fetchData = async (url, reqData, reqType) => {
try {
var statusObj = {
statMsg: null
};
const response = await fetch(url, reqOptions);
if (!response.ok) {
throw response;
}
statusObj.status = "success";
const json = await response.json();
return [json, statusObj];
} catch (error) {
statusObj.status = "error";
return [null, statusObj];
}
}
return fetchData;
}
My questions are;
For the lines
let [state11] = await fetchFn('api/api1', {}, 'GET');
setState1(state11);
I first assign it to a new variable state11 and then assign the same by calling setState1.
Is there a better way to set the state1 directly?
Is the usage of async function inside the useEffect fine ?
For Question 1:
You can directly setState like below without using state11.
useEffect(() => {
(async function () {
try {
setState1(
(await fetchFn("https://reqres.in/api/users/1", {}, "GET"))[0]
); // Is there a better way to set this ?
setState2(
(await fetchFn("https://reqres.in/api/users/2", {}, "GET"))[0]
);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
For Question 2:
I don't see any problem using async & IIFE in the useEffect. In fact I like the way its done. Looks good to me.
Please find screenshot of the state being set properly in the console (I have used a dummy api url):
If you don't want to use async functions, you can use the Promise.prototype.then() method to combine your calls like this :
useEffect(() => {
fetchFn('api/api1', {}, 'GET').then(state => {
setState1(state[0]);
return fetchFn('api/api2', {}, 'GET')
}).then(state => {
setState2(state[0]);
setIsApiCallDone(true);
}).catch(console.log);
}, []);
An other way to set this with an async function but more factorised is this way :
useEffect(() => {
(async function() {
try {
await fetchFn('api/api1', {}, 'GET')
.then(tab => tab[0])
.then(setState1);
await fetchFn('api/api2', {}, 'GET');
.then(tab => tab[0])
.then(setState2);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
Finally, the usage of the async function in an useEffect is not a problem.
I'm going through to update my code for the Firebase version 9 modular form. I am now using onValue. From what I'm reading it returns a function that removes the listener. But I'm still not doing it right because although it functions well at first, when I change the database on the backend with the app open I get the "can't perform a react state update on an unmounted component" error when I'm in a different app screen. See old and new code below please.
OLD CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
let testData = [];
let searchData = [];
db.ref('deals').once('value', (snapshot) =>{
snapshot.forEach((child)=>{
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
})
searchData.push(
child.val().title
)
})
})
.then(()=>{
checkMessages(testData);
setLoading(false);
})
.catch((error) => Sentry.Native.captureException('Error MessagesScreen function loadListings 1 ' + error));
}
NEW CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
After receiving answer below I changed the useEffect to this instead and now it works:
useFocusEffect(
React.useCallback( () => {
async function fetchData() {
// You can await here
const response = await loadListings();
// ...
return () => response();
}
fetchData();
}, [])
);
You mentioned the unsubscribe function returned from onValue. In order to call it, I think you'll want to grab it from the invocation and then call it on some navigation state change.
Assuming you're using React Navigation, it might look something like this (using the useFocusEffect
import { useFocusEffect } from '#react-navigation/native';
function YourComponent() {
const [loading, setLoading] = React.useState(false)
useFocusEffect(
React.useCallback(async () => {
const unsubscribe = await loadListings();
return () => unsubscribe();
}, [])
);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
return <View />;
}
Also don't forget to either use async/await for your asynchronous loadListings function, or use .then(). Otherwise you'll be working with an unresolved promise.
I also found a related StackOverflow question that helped me get to this answer. Maybe that'll be of some use to you.
When I load todos from storage, I get this error. When I replace the loadDate() function with [] this error is not present, but then I cannot get the saved todos from the phone storage. Help me please.The problem may be related to the wrong procedure. I had this before I used useReduce()
const saveData = async () => {
await SecureStore.setItemAsync(STORAGE_KEY, JSON.stringify(state))
}
const loadData = async () => {
try {
const load = JSON.parse(await SecureStore.getItemAsync(STORAGE_KEY) || JSON.stringify([]))
if (load != null) {
return load
}
} catch (e) {
alert (e)
}
}
const [ state, dispatch ] = useReducer(reducer, loadData())
useEffect(() => {
saveData()
}, [state])
im trying to get user location, and then use the location to return relevant data for is location.
but in the second function i get that the location is null (when i console.log(location) it prints the right location, at the second print, the first print is null) it seems like the second function is not waiting until the first one is done.
Here is some code:
from the component
const location = useSelector(state => state.locationReducer.location);
useEffect(()=> {
(async () => {
await getLocation();
// here i'm using the location from the first function
await getInfo(location);
})()
}, []);
const getLocation = async() => {
try {
await dispatch(getLocation());
console.log(location);
} catch (err) {
// TODO HANDLE ERROR;
console.log('Err:', err);
}
}
in the action
export const getLocation = locationName => {
return async dispatch => {
try {
const location = **await** locationService.getLocation(locationName);
**await** dispatch(setLocation(location));
} catch (err) {
throw err;
};
};
};
const setLocation = location => {
return {
type: types.SET_LOCATION,
location
};
};
in service
async function getLocation(locationName) {
try {]
return **await** axios.get(`${url}/${locationName}`);
} catch (err) {
throw err
};
};
The location value from the selector won't update after your first function has run and before the second function, so there you'll see the old value in your location variable.
You might need to return your location value from the reducer:
export const getLocation = locationName => {
return async dispatch => {
try {
const location = await locationService.getLocation(locationName);
await dispatch(setLocation(location));
return location;
} catch (err) {
throw err;
};
};
};
And use the returned location in your useEffect:
useEffect(()=> {
(async () => {
const location = await getLocation();
// here i'm using the location from the first function
await getInfo(location);
})()
}, []);
Or another possibility, to have an another effect, wich depends son the location value:
const location = useSelector(state => state.locationReducer.location);
useEffect(()=> {
getLocation();
}, []);
useEffect(()=> {
if(location) {
getInfo(location);
}
}, [location]);
And this would run every time location changes, and location has some value.
As Per Dan Abramov-
useEffect(() => {
async function fetchMyAPI() {
let url = 'http://something';
let config = {};
const response = await myFetch(url);
console.log(response);
}
fetchMyAPI();
}, []);
Here is the link for reference - https://github.com/facebook/react/issues/14326#issuecomment-441680293
Or You can simply use .then()
useEffect(() => {
asyncCall().then(setVal);
});
Article on how to fetch - https://www.robinwieruch.de/react-hooks-fetch-data