My code is not setting the state of boarditems since whenever useEffect is called, currentUser is null. How do i wait for currentUser to be retrieved before calling useEffect?
const [boarditems, setboarditems] = useState([]);
const currentUser = useContext(CurrentUserContext);
useEffect(() => {
if (currentUser) {
const items = [];
db.collection("boarditems")
.where("userID", "==", currentUser.id)
.get()
.then(query => {
query.forEach(doc => {
items.push({
id: doc.id,
...doc.data()
})
})
setboarditems(items);
})
}
}, [])
Add currentUser to the dependencies array of useEffect(). Whenever the context changes, the component would re-render, and if currentUser changed since the last re-render, and is not null the call would be made:
useEffect(() => {
if (currentUser) {
const items = [];
db.collection("boarditems")
.where("userID", "==", currentUser.id)
.get()
.then(query => {
query.forEach(doc => {
items.push({
id: doc.id,
...doc.data()
})
})
setboarditems(items);
})
}
}, [currentUser]) // set as a dependency
put currentUser in the dependency array for useEffect
const [boarditems, setboarditems] = useState([]);
const currentUser = useContext(CurrentUserContext);
useEffect(() => {
if (currentUser) {
const items = [];
db.collection("boarditems")
.where("userID", "==", currentUser.id)
.get()
.then(query => {
query.forEach(doc => {
items.push({
id: doc.id,
...doc.data()
})
})
setboarditems(items);
})
}
}, [currentUser])
Related
I am trying to add some properties to an array of objects inside useEffect but it renders DOM while those fields are still not present. Is there any reliable way how to wait till those properties are added:
useEffect hook look like this:
useEffect(() => {
onSnapshot(query(collection(db, "conversations"), where('canRead', 'array-contains', user.user.uid), orderBy("lastMsgDate", 'desc')),
async (snapshot) => {
let conversations = snapshot.docs.map(doc => toConversation(doc, user.user.uid));
await conversations.map(async (convo, index) => {
const profile = await getDoc(doc(db, "users", convo.canRead[0]))
conversations[index].picture = profile.data().picture
conversations[index].name = profile.data().name
})
setConversations(conversations)
})
}, []);
This is how I am rendering list of convos:
<IonCard>
{conversations.length > 0 ?
conversations.map((conversation) =>
<IonItem key={conversation.id}>
<IonAvatar slot='start'>
<img src={conversation.picture ? conversation.picture : '/assets/default-profile.svg'} alt={conversation.name} />
</IonAvatar>
<IonLabel>{conversation.name}</IonLabel>
</IonItem>
)
:
<IonCardContent>
no convos
</IonCardContent>
}
</IonCard>
the name and picture does not render even i can see it when log array into console
0:{
canRead: ['iwPmOBesFQV1opgs3HT9rYPF7Sj1'],
id: "W6cefXGoBAZijPof8jVl",
lastMsg: "test",
lastMsgDate: nt {seconds: 1668418292, nanoseconds: 281000000},
lastMsgSender: "Hyw4Argt8rR25mFaFo1Sl4iAWoM2",
name: "Warren"
picture: "https://firebasestorage.googleapis.com/v0/b/..."
users: {
Hyw4Argt8rR25mFaFo1Sl4iAWoM2: true,
iwPmOBesFQV1opgs3HT9rYPF7Sj1: true
}
}
Any help appreciated
You can use a loading message or a gif.
const [loading, setLoading] = useState(true);
useEffect(() => {
onSnapshot(query(collection(db, "conversations"), where('canRead', 'array-contains', user.user.uid), orderBy("lastMsgDate", 'desc')),
async (snapshot) => {
....
setConversations(conversations);
setLoading(false);
})
}, []);
if(loading) return <p>Loading...</p>
return <> your content </>
for some reason it works with setTimeout function which is not the best solution
useEffect(() => {
setLoading({ loading: true, loadingMsg: 'Loading conversations' })
onSnapshot(query(collection(db, "conversations"), where('canRead', 'array-contains', user.user.uid), orderBy("lastMsgDate", 'desc')),
async (snapshot) => {
let convos = snapshot.docs.map(doc => toConversation(doc, user.user.uid));
await convos.map(async (convo, index) => {
const profile = await getDoc(doc(db, "users", convo.canRead[0]))
convos[index].picture = profile.data().picture
convos[index].name = profile.data().name
})
setTimeout(() => {
setConversations(convos)
setLoading({ loading: false, loadingMsg: undefined })
}, 1000);
})
}, [user.user.uid]);
Maybe u can add a loading state? something like
const [loading, setLoading] = useState(false);
const [conversations,setConversations] = useState(null);
const onSnapshot = async () => {
setLoading(true)
onSnapshot(
query(
collection(db, "conversations"),
where("canRead", "array-contains", user.user.uid),
orderBy("lastMsgDate", "desc")
),
async (snapshot) => {
let conversations = snapshot.docs.map((doc) =>
toConversation(doc, user.user.uid)
);
await conversations.map(async (convo, index) => {
const profile = await getDoc(doc(db, "users", convo.canRead[0]));
conversations[index].picture = profile.data().picture;
conversations[index].name = profile.data().name;
});
setConversations(conversations);
}
);
setLoading(false);
};
useEffect(() => {
onSnapshot()
},[])
return loading ? <span>loading...</span> : <div>{conversations.map((con,i) => <span key={i}>con</span>)}</div>
function Home({ isAuth }) {
const [postLists, setPostList] = useState([]);
const postsCollectionRef = collection(db, "posts")
useEffect(() => {
const getPosts = async () => {
const data = await getDocs(postsCollectionRef);
setPostList(data.docs.map((doc) =>
({ ...doc.data(), id: doc.id })));
};
getPosts();
});
Here is a screenshot of the console log. Db says I have over 56k reads.
Looks like your useEffect hook is missing a dependency array. Try replacing it with the following and see if it fixes your issue!
useEffect(() => {
const getPosts = async () => {
const data = await getDocs(postsCollectionRef);
setPostList(data.docs.map((doc) =>
({ ...doc.data(), id: doc.id })));
};
getPosts();
});
}, [])
I'm trying to access a single user data from firebase by passing currentUserId to useCollection hook but currentUser?.id is not available when the application first loads.
this is how I calling my hooks from a component.
const { currentUser } = useAuth()
const { docs = [] } = useCollection("solutions", currentUser?.id, false)
useCollection hook:
const useCollection = (collection, _userID, _openTab) => {
const [docs, setDocs] = useState([])
const [loading, setLoading] = useState(true)
const userID = useRef(_userID).current
const openTab = useRef(_openTab).current
// getting realtime data from the firebase for challenges and solutions
useEffect(() => {
let ref = firestore.collection(collection)
console.log(_userID)
if (openTab && userID) {
console.log("open")
openTab === 1
? (ref = ref.where("userID", "==", userID).where("completed", "==", false))
: (ref = ref.where("userID", "==", userID).where("completed", "==", true))
}
if (userID) {
ref = ref.where("userID", "==", userID)
console.log("first")
}
const unsubscribe = ref.onSnapshot(
(snapshot) => {
const results = []
snapshot.docs.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id })
})
// update state
setDocs(results)
setLoading(false)
},
(error) => {
console.log(error)
}
)
// unsubscribe on unmount
return () => unsubscribe()
}, [collection, openTab, loading]) // eslint-disable-line react-hooks/exhaustive-deps
return { docs, loading }
}
Anyone please help me with this!
I'm having a problem. been browsing some questions here but seems doesn't work for me.
I'm getting this error in my three pages when I'm using the useEffect.
This is the code of my useEffect
const UserDetailsPage = () => {
const classes = useStyles()
const [userData, setUserData] = useState({
_id: "",
name: "",
phone: "",
address: "",
birthdate: "",
gender: "",
messenger: "",
photo: "",
email: "",
})
const [open, setOpen] = useState(false)
const [loaded, setLoaded] = useState(false)
const { height, width } = useWindowDimensions()
const {
_id,
name,
phone,
address,
photo,
gender,
messenger,
birthdate,
email,
} = userData
useEffect(() => {
const user = getUser()
getUserById("/user/" + user.userId, user.token)
.then((data) => {
setUserData(data)
setLoaded(true)
})
.catch((error) => {
console.log(error)
})
}, [])
Short of getUserById returning a cancel token to cancel any inflight network requests, or an "unsubscribe" method, you can use a React ref to track if the component is still mounted or not, and not enqueue the state update if the component has already unmounted.
const isMountedRef = React.useRef(false);
useEffect(() => {
isMountedRef.current = true;
return () => isMountedRef.current = false;
}, []);
useEffect(() => {
const user = getUser();
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if (isMountedRef.current) {
setUserData(data);
setLoaded(true);
}
})
.catch((error) => {
console.log(error);
});
}, []);
This is because of the async call in useEffect finishing and then attempting to setState after the page is no longer in focus.
It can be avoided by refactoring the useEffect like so:
useEffect(() => {
// created a boolean to check if the component is mounted, name is arbitrary
let mounted = true;
const user = getUser();
getUserById("/user/" + user.userId, user.token)
.then((data) => {
// only setState if mounted === true
if (mounted) {
setUserData(data);
setLoaded(true);
}
})
.catch((error) => {
console.log(error);
});
// set mounted to false on cleanup
return () => {
mounted = false;
};
}, []);
What's different here is that I use a mounted boolean to check if the page is currently mounted. By wrapping the setState call inside an if state, I can check if it's safe to setState, therefore avoiding the error.
Additional reading
This happens when your component is unmounting before setting your state. Try this code below to check if the component is mounted or not.
useEffect(() => {
let isMounted = true; // add a flag to check component is mounted
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if(mounted) { // set state only when component is mounted
setUserData(data)
setLoaded(true)
}
})
.catch((error) => {
console.log(error)
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);
Don't use async tasks in useEffect. Define an async function and call in your useEffect.
Example:
const getSTH = async() =>{
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if(mounted) { // set state only when component is mounted
setUserData(data)
setLoaded(true)
}
})
.catch((error) => {
console.log(error)
})
}
useEffect (()=>{
getSTH();
},[])
I think this approach will help you.
Hello I'm trying to display data from firebase, it works very well but when I add the condition where() to compare uid to the id present in my database
However in the console.log, it appears well, it's as if between the user request and the getOrders() request; the delay was too short and that he didn't have time to find uid
const [orders, setOrders] = useState([]);
const [user, setUser] = useState('');
useEffect( () => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user);
console.log(user);
} else {
setUser(null);
}
});
getOrders();
}, []);
function getOrders() {
fire.firestore().collection("orders")
.where('uid', '==', `${user.uid}`)
.where('statut', '==', 'Active')
.onSnapshot(function (querySnapshot) {
setOrders(
querySnapshot.docs.map((doc) => ({
id: doc.id,
orderid: doc.data().id,
statut: doc.data().statut,
nomproduit: doc.data().nomproduit
}))
);
});
}
Thank you for help.
Consider passing the uid as an argument into getOrders() and call it when the user is ready. Like this
const [orders, setOrders] = useState([]);
const [user, setUser] = useState('');
useEffect( () => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user);
console.log(user);
getOrders(user.uid);
} else {
setUser(null);
}
});
}, []);
function getOrders(uid) {
fire.firestore().collection("orders")
.where('uid', '==', `${uid}`)
.where('statut', '==', 'Active')
.onSnapshot(function (querySnapshot) {
setOrders(
querySnapshot.docs.map((doc) => ({
id: doc.id,
orderid: doc.data().id,
statut: doc.data().statut,
nomproduit: doc.data().nomproduit
}))
);
});
}
Another thing you could do would be to pass user as a dependency into the useEffect() hook like this
useEffect( () => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
setUser(user);
console.log(user);
} else {
setUser(null);
}
});
getOrders();
}, [user]);
This will cause the useEffect to run again when the user has changed. Not the best approach tho, performance wise, just FYI.