Realtime data from firestore in react, is this good practice? - reactjs

I made this function that updates with a callback function when the firestore database changes.
export function realTimeData(collectionName, callback, queryForced) {
const dbRef = collection(db, collectionName);
const queryOrdered = query(
collection(db, collectionName),
orderBy("time", "desc"),
limit(50)
);
const unsub = onSnapshot(queryForced || queryOrdered, (snapshot) => {
const result = [];
snapshot.docs.forEach((doc) => {
result.push({
id: doc.id,
...doc.data(),
});
});
unsubs.push(unsub);
console.log("realTimeData", result);
callback({ [collectionName]: result });
});
}
Now I was wondering if this is correct? Does it always update the data when I unmount the react component?
My react component below:
function App() {
const [history, setHistory] = useState({ ttd: [] });
useEffect(() => {
realTimeData("ttd", setHistory);
}, []);
// etc ..

Related

Hi, i'm retrieving data from firestore, and checking whether to direct the user to index page or to enter details for a new user But not able to do so

React code
import React, { useEffect, useState } from "react";
import { getDocs, collection } from "firebase/firestore";
import { auth, db } from "../firebase-config";
import { useNavigate } from "react-router-dom";
function Load() {
const navigate = useNavigate();
const [accountList, setAccountList] = useState([]);
const [hasEmail, setHasEmail] = useState(false);
const accountRef = collection(db, "accounts");
Am i using useEffect correctly?
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
checking whether email exists
const emailCheck = () => {
if (accountList.filter((e) => e.email === auth.currentUser.email)) {
setHasEmail(true);
} else {
setHasEmail(false);
}
};
Redirecting based on current user
const direct = () => {
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
return <div></div>;
}
The code compiles but doesn't redirect properly to any of the pages.
What changes should I make?
First question posted excuse me if format is wrong.
There are two problems here:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
In order:
Since getAccounts is asynchronous, you need to use await when calling it.
But even then, setting state is an asynchronous operation too, so the account list won't be updated immediately after getAccounts completes - even when you use await when calling it.
If you don't use the accountList for rendering UI, you should probably get rid of it as a useState hook altogether, and just use regular JavaScript variables to pass the value around.
But even if you use it in the UI, you'll need to use different logic to check its results. For example, you could run the extra checks inside the getAccounts function and have them use the same results as a regular variable:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
const result = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setAccountList(result);
emailCheck(result);
direct();
};
getAccounts();
}, []);
const emailCheck = (accounts) => {
setHasEmail(accounts.some((e) => e.email === auth.currentUser.email));
};
Alternatively, you can use a second effect that depends on the accountList state variable to perform the check and redirect:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
});
useEffect(() => {
emailCheck();
direct();
}, [accountList]);
Now the second effect will be triggered each time the accountList is updated in the state.

Not getting currentUserID from firebase immediately

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!

onSnapshot firebase, getServerSideProps

my code
export const getServerSideProps: GetServerSideProps = async () => {
const ref = collection(db, "books");
const results = [];
const unsub = onSnapshot(ref, (snapshot) => {
snapshot.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id });
});
//here i get the results
console.log(results)
});
// here is empty
console.log(results)
return {
props: {
books: results,
},
};
};
I'm trying to get the real time data from firestore database on the getServerSideProps function, inside the snapshot I can get the results, but when it's outside the array it's empty and I can't pass to props.
Instead of using onSnapshot I would use getDocs (you need to import from 'firebase/firestore'):
export const getServerSideProps: GetServerSideProps = async () => {
const [results, setResults] = useState([]);
const ref = collection(db, 'books');
const snapshot = await getDoc(docRef);
articlesSnapshot.forEach((doc) => {
setResults((oldArray) => [...oldArray, doc.data()]);
});
return {
props: {
books: results,
},
};
};
I was also stucked in the same problem.
Here we should use get() instead of snapshot cause next will take care of your updated data under the hood.
so rewrite your code like this
export async function getServerSideProps(context) {
const resultref = await db.collection("books").get();
const results = resultRef.docs.map((doc)=>({
id: doc.id,
resultData: doc.data()
}))
return {
props: { books: JSON.parse(JSON.stringify(results)) },
}
}
so map function will return the new array in variable results
which is then deep copied and passed as props
I hope this will give you the desired result 😀

React setState of useState hook successfully updating state, but not re-rendering component

I'm trying to update my old Todo List React app so that it uses hooks, and can't figure this out for the life of me. setTodos is successfully updating the state on load as shown in React developer tools, but the component doesn't re-render and a blank list of todos is displayed on the screen. Only when I add a Todo to the list does it re-render and all of the todos show up. Here's what I've got:
const App = () => {
// Todos are the only state in App
const [ todos, setTodos ] = useState([]);
// Fetches Todos from Firestore database on load
useEffect(() => {
const initialState = [];
dbTodos.get().then((snapshot) => {
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
initialState.push(currentTodo);
});
});
setTodos(initialState);
// eslint-disable-next-line
}, []);
// Add Todo
const addTodo = (title) => {
const newTodo = {
title : title,
completed : false
};
dbTodos.add(newTodo).then((doc) => {
newTodo['id'] = doc.id;
setTodos([ ...todos, newTodo ]);
});
};
Here is the code I used before implementing hooks:
componentDidMount() {
dbTodos.get().then((snapshot) => {
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
setState({ ...state, todos: [ ...state.todos, currentTodo ] });
});
});
};
You should move your setTodos in UseEffect
useEffect(() => {
dbTodos.get().then((snapshot) => {
const initialState = []; // Also this you can move here
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
initialState.push(currentTodo);
});
setTodos(initialState); /// here
});
}, []);
When you setState in useEffect then you have to add setState in dependency.
Instead of doing that use another arrow function and call it from useEffect.
use the code below
const App = () => {
// Todos are the only state in App
const [ todos, setTodos ] = useState([]);
// Fetches Todos from Firestore database on load
const fetchData = () => {
const initialState = [];
dbTodos.get().then((snapshot) => {
snapshot.forEach((doc) => {
const currentTodo = doc.data();
currentTodo['id'] = doc.id;
initialState.push(currentTodo);
});
});
setTodos(initialState);
}
useEffect(() => {
fetchData();
}, []);
// Add Todo
const addTodo = (title) => {
const newTodo = {
title : title,
completed : false
};
dbTodos.add(newTodo).then((doc) => {
newTodo['id'] = doc.id;
setTodos([ ...todos, newTodo ]);
});
};
In useEffect you create empty array. Make a request. set empty array to state. When request was fulfilled you make changes in the array by ref. Therefore you see you array in dev tool and react does'n rerender you component.
const App = () => {
// Todos are the only state in App
const [ todos, setTodos ] = useState([]);
// Fetches Todos from Firestore database on load
useEffect(() => {
dbTodos.get().then((snapshot) => {
setTodos(snapshot.map((doc) => ({
id: doc.id,
...doc.data(),
})));
});
}, []);
// Add Todo
const addTodo = (title) => {
const newTodo = {
title : title,
completed : false
};
dbTodos.add(newTodo).then((doc) => {
newTodo['id'] = doc.id;
setTodos([ ...todos, newTodo ]);
});
};

useState Reactjs not updating

I am trying to use React Hooks but somehow my state is not updating
const [Choices, setChoices] = useState([]);
useEffect(() => {
async function getUsers() {
// Here’s the magic
let tempChoices = [];
const promises = MAINLIST.List.ChoicesFields.map(async z => {
tempChoices[z] = [];
let GetChoicesFromInternalFieldResults = await GetChoicesFromInternalField(
z
);
GetChoicesFromInternalFieldResults.map(c => {
tempChoices[z].push({
key: c,
text: c,
value: c
});
});
});
await Promise.all(promises);
const object = { Choices: tempChoices };
// THIS IS PRINTING CORRECT VALUES
console.log(object);
setChoices(object);
}
getUsers();
}, []);
Here is the console.log result
but when I check the state in the developer tool the state is empty
I found my answers. I made "tempChoices" into an array instead of an object.
const [Choices, setChoices] = useState({});
useEffect(() => {
async function getUsers() {
// Here’s the magic
let tempChoices = {};
const promises = MAINLIST.List.ChoicesFields.map(async z => {
tempChoices[z] = [];
let GetChoicesFromInternalFieldResults = await GetChoicesFromInternalField(
z
);
GetChoicesFromInternalFieldResults.map(c => {
tempChoices[z].push({
key: c,
text: c,
value: c
});
});
});
await Promise.all(promises);
const object = { Choices: tempChoices };
setChoices(object);
}
getUsers();
}, []);

Resources