On my React page I have a DataTable component, the source of which is a data array in my state. In my useEffect I call an action to get the data, map through it and define an object for each row, push it to a new array and do setData to that new array.
It works fine on initial load, but if I click refresh the table is blank and the code doesn't run again
const [data, setData] = useState([])
useEffect(() => {
dispatch(listContractors())
const newData = new Array;
contractors.map(c => {
let obj = {
action: `DELETE | EDIT`,
psa_licence_no: c.psa_licence_no,
company: c.company,
trading_as: c.trading_as,
address_1: c.address_1,
address_2: c.address_2,
address_3: c.address_3,
county: c.county,
eircode: c.eircode,
contact_f_name: c.contact_f_name,
contact_s_name: c.contact_s_name,
office_tel_no: c.office_tel_no,
mobile_tel_no: c.mobile_tel_no,
sentinel_id: c.sentinel_id,
email: c.email,
intruder: c.intruder,
cctv: c.cctv,
guard_static: c.guard_static,
arc_intruder: c.arc_intruder,
arc_cctv: c.arc_cctv,
issue_date: c.issue_date,
expiry_date: c.expiry_date,
psa_date_added: c.psa_date_added,
psa_date_updated: c.psa_date_updated,
psa_date_removed: c.psa_date_removed
}
newData.push(obj)
})
setData(newData)
}, [dispatch])
EDIT:
If I refresh the page from the app, so navigate to a different page and navigate back to this one, it works. The problem only occurs if I click the refresh button on chrome
You are missing contractors in dependency array in useEffect
useEffect(() => {
dispatch(listContractors())
const newData = new Array;
contractors.map(c => {
let obj = {
action: `DELETE | EDIT`,
psa_licence_no: c.psa_licence_no,
company: c.company,
trading_as: c.trading_as,
address_1: c.address_1,
address_2: c.address_2,
address_3: c.address_3,
county: c.county,
eircode: c.eircode,
contact_f_name: c.contact_f_name,
contact_s_name: c.contact_s_name,
office_tel_no: c.office_tel_no,
mobile_tel_no: c.mobile_tel_no,
sentinel_id: c.sentinel_id,
email: c.email,
intruder: c.intruder,
cctv: c.cctv,
guard_static: c.guard_static,
arc_intruder: c.arc_intruder,
arc_cctv: c.arc_cctv,
issue_date: c.issue_date,
expiry_date: c.expiry_date,
psa_date_added: c.psa_date_added,
psa_date_updated: c.psa_date_updated,
psa_date_removed: c.psa_date_removed
}
newData.push(obj)
})
setData(newData)
}, [dispatch, contractors])
I recommend using eslint to catch this type of bugs.
Related
PersonalSlice.js
export const updatePersonal = createAsyncThunk("personal/updatePersonal", async (id, {getState})=>{
const personalDoc = doc(db, "Personal", id)
await updateDoc(personalDoc, getState().personal.updatePersonal );
});
Main.js
const updateName = useSelector((state) => state.personal.updatePersonal.name);
const handleUpdateNameChange = (e) => {
dispatch(changeUpdatePersonalName(e.currentTarget.value))
}
const handleUpdateSubmit = (e) => {
e.preventDefault();
dispatch(updatePersonal({updateName , updateSurname, updateBirthday, updateStartDate, updateDepartment, updatePhone, updateMail}))
}
<Form onSubmit={handleUpdateSubmit}>
<p className="text-center" style={{ color: "#39ace7" }}>İsim</p>
<Form.Control
size = "sm"
type="text"
name="updatePersonal"
onChange={handleUpdateNameChange}
value={updateName}
required
autoFocus
/>
I can't send the data I want to update in the form to FireBase. My fault is most likely in the parameters.
If you are starting a new project and/or are not required to have your Firebase data loaded into redux, you might want to give reactfire a try before trying react-redux-firebase. In reference to the above function call ,if the profile object contains a key or a list of keys as parameters, you can populate those parameters with the matching value from another location on firebase.
Setting config like this:
userProfile: 'users', // where profiles are stored in database
profileParamsToPopulate: [
'contacts:users'
]
}
Results in profile with populated contacts parameter:
displayName: 'ABC',
email: 'abc#xyz.com',
contacts: [
{
email: 'efg#xyz.com',
displayName: 'EFG'
},
{
email: 'pqr#xyz.com',
displayName: 'PQR
}
]
}
Please check the link here to understand the parameters call usage and related questions Update Data in Firebase and Update function in Firebase for reference.Also check the helpful documentation for configuration for redux-firebase
This is probably a noob question, but I'm facing some troubles with the useEffect() hook. I have a Taking Notes App, and I want to make the data persist. I'm using 2 useEffects: one for when the page is refreshed/loaded by the first time, and other one for when I add a new note to my app.
I putted some logs to check what's happening:
const [notes, setNotes] = useState([
{
noteId: nanoid(),
text: 'This is my 1st note!',
date: '30/07/2022'
},
{
noteId: nanoid(),
text: 'This is my 2nd note!',
date: '30/07/2022'
}
])
// 1st time the app runs
useEffect(() => {
const savedNotes = JSON.parse(localStorage.getItem('react-notes'))
console.log('refresh page call:',savedNotes)
if(savedNotes) {
setNotes(savedNotes)
}
}, [])
//every time a new note is added
useEffect(() => {
localStorage.setItem('react-notes', JSON.stringify(notes));
console.log('new note call:', notes)
}, [notes])
The behaviour is a bit strange, because when the page is refreshed the new data is appearing inside the log, but then it disappears, maintaining only the hardcoded data:
It also makes more calls than I was expecting to. Any thoughts about what is going on here?
Issue
The problem is caused by the below useEffect and how you are initially setting the state:
useEffect(() => {
localStorage.setItem('react-notes', JSON.stringify(notes));
console.log('new note call:', notes)
}, [notes])
The above useEffect runs every time notes changes, but also on mount. And on mount the state is equal to that initial array given to useState. So the localStorage is set to that array.
Solution
A solution is to change how you are setting the state as below, so you pick what's in the localStroge if there is something, and otherwise use that initial array you have:
const [notes, setNotes] = useState(
!localStorage.getItem("react-notes")
? [
{
noteId: nanoid(),
text: "This is my 1st note!",
date: "30/07/2022",
},
{
noteId: nanoid(),
text: "This is my 2nd note!",
date: "30/07/2022",
},
]
: JSON.parse(localStorage.getItem("react-notes"))
);
here is my problem
I want to loop data from two entities Project and User, my Project entity have a field with User id called creatorId.
I looped it and tried to add my user data _id, displayname and avatarUrl.
For now that work, but when I send it from the backend to the frontend nothing appears, like the array is empty.
const projectList = [];
Project.find({}, function(err, projects) {
projects.map(project => {
User.findById(project.creatorId, function(err, creator){
projectList.push({
_id: project._id,
title: project.title,
description: project.description,
avatarUrl: project.avatarUrl,
creationDate: project.creationDate,
projectCreator: {
_id: creator._id,
displayname: creator.displayname,
avatarUrl: creator.avatarUrl
},
git: project.git
})
})
})
});
res.send(projectList);
I am building an order form that limits how many items you can order based on the stock of the item. I have a menu collection which has items
// menu
{ id: "lasagna", name: "Lasagna", price: 10, stock: 15 }
{ id: "carrot-soup", name: "Carrot Soup", price: 10, stock: 15 }
{ id: "chicken-pot-pie", name: "Chicken Pot Pie", price: 10, stock: 15 }
And an orders collection
// orders
{ id: <auto>, name: "Sarah", cart: {lasagna: 1, carrot-soup: 3}, ... }
{ id: <auto>, name: "Wendy", cart: {chicken-pot-pie: 2, carrot-soup: 1}, ... }
{ id: <auto>, name: "Linda", cart: {lasagna: 3}, ... }
4 carrot-soup has been ordered so the stock should be updated
// updated stock
{ id: "carrot-soup", name: "Carrot Soup", stock: 11 }
Orders are inserted from my Form component
function Form(props) {
// ...
// send order to firestore
const onSubmit = async _event => {
try {
const order = { cart, name, email, phone, sms }
dispatch({ action: "order-add" })
const id = await addDocument(store, "orders", order)
dispatch({ action: "order-add-success", payload: { ...order, id } })
}
catch (err) {
dispatch({ action: "order-add-error", payload: err })
}
}
return <form>...</form>
}
This is my database addDocument function
import { addDoc, collection, serverTimeStamp } from "firebase/firestore"
async function addDocument(store, coll, data) {
const docRef = await addDoc(collection(store, coll), { ...data, timestamp: serverTimestamp() })
return docRef.id
}
How should I decrement the stock field in my menu collection?
Ideally the client should have only read access to menu but to update the stock the client would need write access.
Another possibility is to have the client query the orders, sum the items, and subtract them from the read-only menu. But giving the client read access to other people's orders seems wrong too.
I am new to firestore and don't see a good way to design this.
You should deffinitely use a cloud function to update the stock. Create a function onCreate and onDelete functions trigger. If users can change data you would also need to onWrite function trigger.
Depending on the amount of data you have you woould need to create a custom queue system to update the stock. Belive me! It took me almost 2 years to figure out to solve this. I have even spoken with the Firebase engeeners at the last Firebase Summit in Madrid.
Usualy you would use a transaction to update the state. I would recommend you to do so if you don't have to much data to store.
In my case the amount of data was so large that those transactions would randomly fail so the stock wasn't correct at all. You can see my StackOverflow answer here. The first time I tought I had an answer. You know it took me years to solve this because I asked the same question on a Firebase Summit in Amsterdam. I asked one of the Engeeners who worked on the Realtime Database before they went to Google.
There is a solution to store the stock in chunks but even that would cause random errors with our data. Each time we improved our solution the random errors reduced but still remained.
The solution we are still using is to have a custom queue and work each change one by one. The downside of this is that it takes some time to calculate a lot of data changes but it is 100% acurate.
Just in case we still have a "recalculator" who recalculates one day again and checks if everything worked as it should.
Sorry for the long aswer. For me it looks like you are building a similar system like we have. If you plan to create a warehouse management system like we did I would rather point you to the right direction.
In the end it depends on the amount of data you have and how often or fast you change it.
Here is a solution based on Tarik Huber's advice.
First I include functions and admin
const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp()
Then I create increment and decrement helpers
const menuRef = admin.firestore().collection("menu")
const increment = ([ id, n ]) =>
menuRef.doc(id).update({
stock: admin.firestore.FieldValue.increment(n)
})
const decrement = ([ id, n ]) =>
increment([ id, n * -1 ])
Here is the onCreate and onDelete hooks
exports.updateStockOnCreate =
functions
.firestore
.document("orders/{orderid}")
.onCreate(snap => Promise.all(Object.entries(snap.get("cart") ?? {}).map(decrement)))
exports.updateStockOnDelete =
functions
.firestore
.document("orders/{orderid}")
.onDelete(snap => Promise.all(Object.entries(snap.get("cart") ?? {}).map(increment)))
To handle onUpdate I compare the cart before and after using a diff helper
exports.updateStockOnUpdate =
functions
.firestore
.document("orders/{orderid}")
.onUpdate(snap => Promise.all(diff(snap.before.get("cart"), snap.after.get("cart")).map(increment)))
Here is the diff helper
function diff (before = {}, after = {}) {
const changes = []
const keys = new Set(Object.keys(before).concat(Object.keys(after)))
for (const k of keys) {
const delta = (before[k] ?? 0) - (after[k] ?? 0)
if (delta !== 0)
changes.push([k, delta])
}
return changes
}
I'm trying to think of a better way to do my onChange validation for my form but it's really laggy because of the multiple rerenders.
This is my useEffect code:
useEffect(() => {
if (passwordValues.password) {
setValidPassword({ confirmPassword: validateConfirmPassword(correctPassword), password: validatePassword(correctPassword) })
}
if(formData.name){
setValidFormData(validFormData => ({...validFormData, name: validateData(correctFormData, "name")}))
}
if(formData.lastName){
setValidFormData(validFormData => ({...validFormData, lastName: validateData(correctFormData, "lastName")}))
}
if(formData.email){
setValidFormData(validFormData => ({...validFormData, email: validateData(correctFormData, "email")}))
}
if(formData.phone){
setValidFormData(validFormData => ({...validFormData, phone: validateData(correctFormData, "phone")}))
}
}, [passwordValues, correctPassword, correctFormData, formData])
I know I can maybe do that in just a couple lines but is that what is doing so many rerenders?
My formData, passwordValues, correctPassword and correctFormData change on every input change.
-- EDIT --
I removed most of the dependencies in the array and I just stayed with [formData], improves the speed, but still quite a bit laggy.
I am writing it here, because it will be a bit long explanation, not suitable for comments.
These scenarios are pretty common in any application , where you have to react to changes in values and to tackle this I just created one special hook, use-effect-x (Writing tests is pending)
This custom hook will tell you the changed item set, which can be very useful here. Below is the code , that I was able to write based on your inputs. you can make use of useEffectX as a replacement of useEffect everywhere. This way non-needed setValidFormData will not run. Give it a try and let me know.
import { useEffectX } from "use-effect-x";
useEffectX(
({
changedItem: [
changeObjConfirmPassword,
changeObjPassword,
changeObjName,
changeObjLastname,
changeObjEmail,
changeObjPhone,
],
}) => {
if (changeObjConfirmPassword.changed) {
setValidPassword({
confirmPassword: validateConfirmPassword(confirmPassword),
});
}
if (changeObjPassword.changed) {
setValidPassword({
password: validatePassword(correctPassword),
});
}
if (changeObjName.changed) {
setValidFormData((validFormData) => ({
...validFormData,
name: validateData(correctFormData, "name"),
}));
}
if (changeObjLastname.changed) {
setValidFormData((validFormData) => ({
...validFormData,
lastName: validateData(correctFormData, "lastName"),
}));
}
if (changeObjEmail.changedd) {
setValidFormData((validFormData) => ({
...validFormData,
email: validateData(correctFormData, "email"),
}));
}
if (changeObjPhone.changed) {
setValidFormData((validFormData) => ({
...validFormData,
phone: validateData(correctFormData, "phone"),
}));
}
},
[confirmPassword, password, name, lastName, email, phone]
);
Thanks and let me know, if this is not the suggestion you were expecting, I will move it.