Get data from Firebase with React - javascript

function Editor({ userObj }) {
const [myContents, setMyContents] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const db = dbService.collection("contents").onSnapshot((snapshot) => {
if (snapshot.size) {
const communityArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMyContents(communityArray);
setLoading(false);
console.log(loading);
} else {
setLoading(false);
}
});
return () => {
db();
};
}, []);
setTimeout(() => {
console.log(loading);
console.log("4", myContents[0].id);
}, 1000);
I don't know why the message appears so many times.
When I print console.log(loading) after setLoading(false), I don't know why it shows true.
When using the setTimeout function, the id value is displayed after an error.
Is it necessary to use async for the id to come out properly?

You can check React.StrictMode in app.js or index.js, it will duplicate render. You can try remove it.
You want it run in your code, pls check it has a value before log
console.log("4", myContents[0] && myContents[0].id);
After that useEffect run, it will re-call setTimeout when it call setMyContents(communityArray); setLoading(false);

Related

Access variable outside useEffect in React.js

I am trying to fetch data as soon as page loads using useEffect(), but the problem is I don't know where to declare stateful const [orders, setOrders] = useState([]); when I put it above main function ( trying to make it global ) I get error saying React useState cannot be called top level. When I move it inside the function I cannot access it from fetchdata() function. I have to use setOrders from inside fetchdata()
What is the proper way to do it ?
function ActiveOrders () {
const [orders, setOrders] = useState([]);
useEffect(()=>{
fetchPost();
}, [])
}
const fetchPost = async () => {
await getDocs(collection(db, "orders"))
.then((querySnapshot)=>{
const newData = querySnapshot.docs
.map((doc) => ({...doc.data(), id:doc.id }));
setOrders(newData);
console.log(orders, newData);
})
}
Error I am getting : 'setOrders' is not defined
'orders' is not defined
The function fetchPost() should be in your component scope:
function ActiveOrders() {
const [orders, setOrders] = useState([]);
useEffect(() => {
fetchPost();
}, []);
const fetchPost = async () => {
await getDocs(collection(db, "orders")).then((querySnapshot) => {
const newData = querySnapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setOrders(newData);
console.log(orders, newData);
});
};
// Then, return()...
}
Move } after useEffect to end of the function for this problem. Next problem is that you are using "await" within "then" try write this code:
const {docs} = await getDocs(collection(db, "orders"))
const newData = docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setOrders(newData);
console.log(orders, newData)

Run React useEffect if the inputed string is the same as before

I am trying to make my own useFetch hook.
export const useFetch = <T extends unknown>(
url: string,
options?: RequestInit
) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [response, setResponse] = useState<T>();
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(url, { ...options, signal: controller.signal })
.then((res) => {
if (!res.ok) {
setError(true);
} else {
setError(false);
}
return res.json();
})
.then((json) => {
setLoading(false);
setResponse((json as unknown as JSONResponse).content as T);
})
.catch((err) => {
console.log(err);
setLoading(false);
setError(true);
});
return () => {
controller.abort();
};
}, [url, options]);
return { loading, error, response };
};
The fetch runs every time the URL changes. My problem is that I use it like this in my Home.tsx
const [url, setUrl] = useState('')
const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>;
const { error, response, loading } = useFetch<ExistsRoom>(
url
);
const joinRoom = async () => {
const id = roomIDRef.current.value;
if (id === '') {
return;
}
setUrl(() => `http://localhost:8808/api/v1/room/exists?id=${id}`);
};
The user has to input an id and press a button to run this fetch. It should check if the room with this id exists. My problem is if the user tries to check the room with the id=test and the request will be made he gets his response. But if he tries to press the button again, which will mean he will request the same id and therefore the same url it won't fetch again because the url doesn't change.
Can I work around this somehow?
I tried to add a random query parameter to the URL and it works
setUrl(() => `http://localhost:8808/api/v1/room/exists?id=${id}&t=${Date.now().toString()}`);
but I don't think this is the cleanest way to do it.
Add a counter and every time that use clicks the button again, raise the counter. Add counter to dependencies.

How to wait async data to start sync function

I get some data from an api call and set them in a state. Then I use this state variable in another function to filter some data. When the user opens the interface for the first time the data doesnt show because the sync function gets the empty data from the state.
Here is the code :
const [evQuestion, setEvQuestion] = useState();
const [answers, setAnswers] = useState();
const getEvaluationsQuestionsByOrganizations = async (evalId) => {
const response = await apiStandarts.get(`/evaluation-questions?organization_evaluation=${evalId}`);
setEvQuestion(response.data);
};
const evAnswers = () => {
const evAnswers = questions.map(q => {
return evQuestion?.map(ev => {
return q.question_options.find(i => i.id === ev.questOptionId)
});
});
const filterAnswers = evAnswers.map(q => {
return q?.filter(Boolean)
})
const answersToObject = filterAnswers.map(item => {
return convertArrayToObject(item)
});
const arr = {...answersToObject}
const obj2 = Object.fromEntries(
Object.entries(arr).map(([key, value]) => [key, value])
)
const obj3= Object.values(obj2).map(item => {
return {[item.question]: {...item}}
})
const savedAnswers = convertArrayToObject(obj3);
console.log(savedAnswers)
setAnswers(savedAnswers)
}
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
evAnswers();
}, [])
I've tried to wrap the evAnswers function in a settimeout function but with no luck. How can I achieve this, any ideas?
Try adding another useEffect hook that depends on evQuestion state.
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
}, []);
useEffect(() => {
evAnswers();
}, [evQuestion]);
the function getEvaluationsQuestionsByOrganizations(..) is defined as async function, but you are using it synchronously, in that case you should call your codes as below:
useEffect(() => {
const fetchedDataAPI = async () => {
return await getEvaluationsQuestionsByOrganizations();
};
fetchedDataAPI
.then(res => { evAnswers();})
.catch(err => {..});
;
}, []);

React Native memory leak error after updating to Firebase version 9 onValue

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.

useState set call not reflecting change immediately prior to first render of app

New to React Hooks and unsure how to solve. I have the following snippet of code within my App.js file below.
What I am basically trying to achieve is to get the user logged in by calling the getUser() function and once I have the user id, then check if they are an authorised user by calling the function checkUserAccess() for user id.
Based on results within the the validIds array, I check to see if it's true or false and set authorised state to true or false via the setAuthorised() call.
My problem is, I need this to process first prior to performing my first render within my App.js file.
At the moment, it's saying that I'm not authroised even though I am.
Can anyone pls assist with what I am doing wrong as I need to ensure that authorised useState is set correctly prior to first component render of application, i.e. path="/"
const [theId, setTheId] = useState('');
const [authorised, setAuthorised] = useState(false);
const checkUserAccess = async (empid) => {
try {
const response = await fetch("http://localhost:4200/get-valid-users");
const allUsers = await response.json();
const validIds = allUsers.map(({ id }) => id);
const isAuthorised = validIds.includes(empid);
if (isAuthorised) {
setAuthorised(true)
} else {
setAuthorised(false)
}
} catch (err) {
console.error(err.message);
}
}
const getUser = async () => {
try {
const response = await fetch("http://localhost:4200/get-user");
const theId= await response.json();
setTheId(theId);
checkUserAccess(theId);
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
getUser();
}, []);
Unless you are wanting to partially render when you get the user ID, and then get the access level. There is no reason to have multiple useState's / useEffect's.
Just get your user and then get your access level and use that.
Below is an example.
const [user, setUser] = useState(null);
const checkUserAccess = async (empid) => {
const response = await fetch("http://localhost:4200/get-valid-users");
const allUsers = await response.json();
const validIds = allUsers.map(({ id }) => id);
const isAuthorised = validIds.includes(empid);
return isAuthorised;
}
const getUser = async () => {
try {
const response = await fetch("http://localhost:4200/get-user");
const theId= await response.json();
const access = await checkUserAccess(theId);
setUser({
theId,
access
});
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
getUser();
}, []);
if (!user) return <div>Loading</div>;
return <>{user.theId}</>
This way it should work
but keep in mind that you must render your app only if theId in the state is present, which will mean your user is properly fetched.
const [state, setState] = useState({ theId: '', isAutorized: false })
const getUser = async () => {
try {
const idResp = await fetch("http://localhost:4200/get-user");
const theId = await idResp.json();
const authResp = await fetch("http://localhost:4200/get-valid-users");
const allUsers = await authResp.response.json();
const validIds = allUsers.map(({ id }) => id);
const isAuthorised = validIds.includes(theId);
setState({ theId, isAuthorised })
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
getUser();
}, []);
if (!state.theId) return <div>Loading</div>;
if (state.theId && !isAuthorized) return <AccessNotAllowed />
return <Home />

Categories

Resources