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"))
);
Related
I am developing a component where I will get the data from a call back function. Initially the state of the component will be empty [], later once the callback function is called I need to update the values into the state. At a time I'll recive only one array, meaning user can add one item at a time that item will consists of nested objects and array values. I have added the logic for the same to handle the scenario, but when I am testing in jest when I am trying to add another set of item from mock meaning the user can select next item when the done with selecting and submitting the first item at that time my logic is getting failed, I am not getting where I went wrong, could any one help me to resolve this issue, thanks in advance! I have added the mock data structure and logic and jest test below.
Mock:
const items = {
itemList: {
itemOne: [{
id: "01",
category: "It-A",
isCreated:"true"
}],
itemDesc:[{
id:"01",
type:"A-1",
isCreated:"true"
}]
}
ItemID:'123'
}
Code:
class ItemComp extends React.Component{
this.state = {
processingItems:[]
onAddItemHandle = (processingItem) => {
this.setState(prevState => ({
processingItems: [...prevState.processingItems, processingItem]
}))
}
JEST:
describe('handleonAddItem', () => {
it('should allow to add multiple items based on prevState', () => {
const compView = mountWithIntl(
<compView
itemId={12}
/>
}
const instance = compView.find(compViewComponent).instance();
instance.onAddItemHandle(items) // when I am giving only one instance my logic is working
instance.onAddItemHandle(items) //when I am giving it for second time it's failing I am getting error like expected - 0 , received +18 I want to update the items here when user clicks for second time but it is failing.
expect(instance.state.processingItems).toEqual([items])
Missing a ',' before the ItemID is the only issue I faced while reproducing.- https://codesandbox.io/s/intelligent-chaplygin-0ot56e?file=/src/App.js
const items = {
itemList: {
itemOne: [{
id: "01",
category: "It-A",
isCreated:"true"
}],
itemDesc:[{
id:"01",
type:"A-1",
isCreated:"true"
}]
},
ItemID:'123'
}
I've got my <MockedProvider /> set up passing in mocks={mocks}. everything is working, all good.
the issue is I have a form that whenever any part of it is edited then this makes a mutation, which returns a response and updates the total. say for example, quantity is changed, mutation increases quantity from 1 to 2. total price should double
problem is that in unit tests and mocked provider you only test the functionality in props and hardcoded response. it's not a proper test. perhaps it's more of an e2e/integration test but I was wondering if there's anything you can do with MockedProvider that allows for better testing in this situation?
Instead of using the normal static result property of the objects in the mocks array, you can set a newData function that will run dynamically and use whatever is returned as the result value. For example:
let totalNoteCount = 0;
const mocks = [{
request: {
query: CREATE_NOTE,
variables: {
title: 'Aloha!',
content: 'This is a note ...',
},
},
newData: () => {
// do something dynamic before returning your data ...
totalNoteCount += 1;
return {
data: {
createNote: {
id: 1,
totalNoteCount,
},
},
};
}
}];
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
}
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.
I'm just making a simple form component that reads in the user information and displays it in the form as values. After being edited the change can be saved or canceled. However the local state updates it's parent state which has got me scratching my head.
I have a parent component with a user info object inside its state object:
usrInfo: {
userName: {name: 'usrname', title: "User Name", value: 'state.user'},
firstName: {name: 'fname', title: "First Name", value: "asdf"},
lastName: {name: 'lname', title: "Last Name", value: "asdf"},
title: {name: 'title', title: "Title", value: "asdf" },
email: {name: 'email',title: "E-mail",value: "asdf#asdf.com"}
},
my child component displays this state no problem. An edit button is clicked in the child and calls a function also in the child to set the userCashe child state to it's parrent user state:
casheState() {;
this.setState((prevState, props) => {
return {userInfoCashe: props.user };
})
}
then a form populates with the userInfoCash as values and when edited it updates the child state:
changeHandler = (event) => {
let usrCopy = {...this.state.userInfoCashe};
usrCopy[event.target.id].value = event.target.value;
this.setState({userInfoCashe: usrCopy});
console.log(this.state.userInfoCashe[event.target.id].value)
console.log(this.props.user[event.target.id].value)
// ^^ these are the same, why?
};
this function mutates it's parent user state. HOW AND WHY IS THIS HAPPENING?! I thought that react was built on one-way data binding.
Thanks!
this.setState doesn't update the state immediately. Its async. So if your console.log shows the same state before and after, then its because of this only.
You can try doing something like this :
this.setState({userInfoCashe: usrCopy}, ()=> {console.log(this.state.userInfoCashe);})
to actually see if your state got mutated. Hope this helps.
first I would like to suggest to use lodash npm it will support javascript functions
reference link : https://www.npmjs.com/package/lodash
install lodash : npm install --save lodash
after installing it import it in your file wherever you want to use
import _ from "lodash";
changeHandler = (event) => {
let usrCopy = _.cloneDeep(...this.state.userInfoCashe);
usrCopy[event.target.id].value = event.target.value;
this.setState({userInfoCashe: usrCopy});
console.log(this.state.userInfoCashe[event.target.id].value)
console.log(this.props.user[event.target.id].value)
// ^^ these are the same, why?
};
please try this, it will not update you original state it will update only the where took the copy of state.